packages feed

fourmolu 0.19.0.1 → 0.21.0.0

raw patch · 377 files changed

Files

CHANGELOG.md view
@@ -1,6 +1,193 @@+## Fourmolu 0.21.0.0++* Import lines separated by comments without blank lines are now considered one import group++### Upstream changes:++#### Ormolu 0.9.0.0++* Comments are now attached to the syntax tree by position, before anything+  is printed, rather than by a cursor advanced as the printer walks the+  tree. Which element owns a comment no longer depends on the order in which+  the printer happens to visit things, so comments stop escaping the+  construct they were written in when Ormolu sorts or regroups it: a comment+  inside an import list stays there, a comment after a quasi-quote stops+  floating to the bottom of the file, and a comment attached to an import+  travels with that import when the imports are sorted. [Issue+  1074](https://github.com/tweag/ormolu/issues/1074) and [issue+  1076](https://github.com/tweag/ormolu/issues/1076).++* Haddock comments are now printed as they were written instead of being+  rebuilt from the documentation string GHC parsed out of them. A `{- | …+  -}` stays a block comment rather than becoming `--` lines, an empty `-- |`+  is no longer dropped, and a `{- *** … -}` section heading keeps its+  meaning. [Issue 641](https://github.com/tweag/ormolu/issues/641), [issue+  822](https://github.com/tweag/ormolu/issues/822), and [issue+  1159](https://github.com/tweag/ormolu/issues/1159).++  Ormolu still puts a space after a Haddock's trigger, re-indents a block+  Haddock to line up with the code it documents, and rewrites a trailing `--+  ^ X` as a leading `-- | X` when it moves the comment in front of what it+  documents.++* Backslashes are no longer added to lines in the middle of a comment block,+  where Haddock does not look for a trigger anyway. [Issue+  1131](https://github.com/tweag/ormolu/issues/1131).++* A comment written on its own line in front of an operator no longer+  strands the operator at the start of the next line. In a `do` block that+  changed what the code meant, because `$` at the beginning of a line is+  read as a new statement rather than as a continuation of the previous one.+  [Issue 1028](https://github.com/tweag/ormolu/issues/1028).++* A comment written after `=`, `->`, or a lambda arrow now stays on that+  line instead of being pushed onto the next one, and the result is+  idempotent. `f x = -- note` no longer becomes an `=` stranded on a line of+  its own. [Issue 786](https://github.com/tweag/ormolu/issues/786), [issue+  810](https://github.com/tweag/ormolu/issues/810), and [issue+  936](https://github.com/tweag/ormolu/issues/936).++* Layout decisions now take comments into account. A comment that falls+  inside a construct can no longer be squeezed into a single-line rendering+  of it.++* A comment block that trails a line of code and continues below it no+  longer drops to the start of the line, which could put the rest of the+  block outside the construct it was written in.++* A construct that brackets its contents is no longer put on one line when+  something inside it is documented with a `-- |` Haddock. Such a Haddock+  takes whole lines, so it used to swallow the closing bracket: a documented+  `deriving` clause came out as `deriving (-- | B`, and a documented field+  of a short record as `{-- | …`, which did not even parse. [Issue+  752](https://github.com/tweag/ormolu/issues/752) and [issue+  1164](https://github.com/tweag/ormolu/issues/1164).++  A `{- | … -}` Haddock is self-delimiting and does not force anything, so+  a declaration documented that way is left as it was written rather than+  being broken up: `data A = A {- | a number -} Int Bool` stays on one line+  where it used to be spread over five.++* Only pragmas in the file header are hoisted to the top of the module now.+  A `LANGUAGE` or `OPTIONS_GHC` pragma written after the first import or+  declaration stays where it is, and no longer drags the comments above it+  to the top of the file. GHC reads the header and stops, so such a pragma+  never affected compilation; moving it was giving it an effect it did not+  have. [Issue 1168](https://github.com/tweag/ormolu/issues/1168).++* A comment above a `{-# LANGUAGE A, B #-}` pragma is no longer duplicated+  when the pragma is split into one per extension; it stays with the first.+  [Issue 787](https://github.com/tweag/ormolu/issues/787).++* Ormolu now checks that the comments of the output correspond to the+  comments of the input—none dropped, duplicated, invented, or reordered—and+  refuses to format when they do not. This runs alongside the existing check+  that the AST is unchanged, is disabled by `--unsafe`, and costs nothing+  extra: the printer already records where it put each comment.++## Fourmolu 0.20.1.0++### Upstream changes:++#### Ormolu 0.8.2.0++* Overhaul how operator fixity information is collected. In addition to the+  Hoogle database, Ormolu now parses the sources of a curated set of important+  packages directly, which yields more accurate and complete fixity data than+  before. In particular it recovers fixities for operators re-exported through+  umbrella modules (e.g. `Servant.API`, `Control.Lens`) that recent Hoogle+  databases no longer record. As a result, formatting of operator chains is+  improved out of the box, and users should expect some operator-heavy code to+  be laid out differently (and more correctly) than in previous releases.++* Improve the layout of chains of `infixr 0` operators (`$`, `seq`, `?:`, and+  the like). Such operators are only laid out in the trailing "staircase" style+  when it is warranted: either the chain consists of a single operator, or its+  final operand is a hanging construct (a `do` block, lambda, `case`, etc.). A+  chain of several such operators that ends in an ordinary expression is now+  laid out with the operators in the leading position instead of an+  ever-deepening pyramid. [Issue+  1151](https://github.com/tweag/ormolu/issues/1151).++* Do not crash when a parent directory cannot be read due to insufficient+  permissions while searching for configuration files; the search for+  configuration files is stopped at that point instead. [Issue+  1212](https://github.com/tweag/ormolu/issues/1212).++* Preserve blank lines between blocks in layout contexts (`where`, `do`,+  `let`) when the preceding block ends with a trailing comment. [Issue+  1132](https://github.com/tweag/ormolu/issues/1132).++* Fix printing of single line export lists with inlined Haddock comments.+  [Issue 1051](https://github.com/tweag/ormolu/issues/1051).++* Fix preservation of the position of comments around the `where` keyword.+  [Issue 784](https://github.com/tweag/ormolu/issues/784).++* Do not sort `Prelude` to the end of the import list when the+  `NoImplicitPrelude` extension is enabled; instead sort it like any other+  import. [Issue 1189](https://github.com/tweag/ormolu/issues/1189).++## Fourmolu 0.20.0.0++Now installable with GHCup! See README for details.++* Improve `import-grouping` configuration++    * Add the `scope` option for import grouping ([#525](https://github.com/fourmolu/fourmolu/pull/525)).++    * Deprecate the `match` option for import grouping in favor of `glob` and `scope` ([#525](https://github.com/fourmolu/fourmolu/pull/525)).++    * Add the `import-list` option for import grouping ([#522](https://github.com/fourmolu/fourmolu/pull/522)).++* Add a `record-style` to configure the placement of record braces. ([#467](https://github.com/fourmolu/fourmolu/issues/467))++* Fix applying `indent-wheres` configuration to pattern synonyms with explicit bidirectional `where` clauses ([#486](https://github.com/fourmolu/fourmolu/issues/486))++### Upstream changes:++#### Ormolu 0.8.1.1++* Add missing braces for case expressions in single‑line do blocks. [Issue 1180](https://github.com/tweag/ormolu/issues/1180).++* Fix the import grouping logic in the presence of imports with explicit levels. [Issue 1192](https://github.com/tweag/ormolu/issues/1192).++#### Ormolu 0.8.1.0++* Fix printing of guards on pattern binds. [Issue+  1178](https://github.com/tweag/ormolu/issues/1178).++* Switched to `ghc-lib-parser-9.14`, with the following new syntactic features:+   * GHC proposal [#493](https://github.com/ghc-proposals/ghc-proposals/blob/e2c683698323cec3e33625369ae2b5f585387c70/proposals/0493-specialise-expressions.rst): expressions in SPECIALISE pragmas+   * Multiline strings in foreign import declarations.+   * `ExplicitNamespaces` supports the `data` namespace specifier in import and export lists, replacing `pattern`.+   * `LinearTypes` adds new syntax to support non-linear record fields.+   * `RequiredTypeArguments` allows visible forall in GADT syntax.++* Updated to `Cabal-syntax-3.16`.++* Correctly format string literals containing the `\^\` escape sequence. [Issue+  1165](https://github.com/tweag/ormolu/issues/1165).++* Correctly preserve consecutive blank lines in multiline strings. [Issue+  1194](https://github.com/tweag/ormolu/issues/1194).++* Fix printing of multi-line or-patterns inside as-patterns. [Issue+  1183](https://github.com/tweag/ormolu/issues/1183).++* Fix an issue where or-patterns would be indented twice. [Issue+  1188](https://github.com/tweag/ormolu/issues/1188).++* Add support for `ExplicitLevelImports`. [Issue+  1192](https://github.com/tweag/ormolu/issues/1192).+ ## Fourmolu 0.19.0.1 -* Fixed a performance regression introduced in 0.8.0.0. [Issue+### Upstream changes:++#### Ormolu 0.8.0.2++* Fix a performance regression introduced in 0.8.0.0. [Issue   1176](https://github.com/tweag/ormolu/issues/1176).  ## Fourmolu 0.19.0.0@@ -429,7 +616,7 @@  #### Ormolu 0.5.0.1 -* Fixed a bug in the diff printing functionality. [Issue+* Fix a bug in the diff printing functionality. [Issue   886](https://github.com/tweag/ormolu/issues/886).  * Indent closing bracket for list comprehensions in `do` blocks.@@ -722,7 +909,7 @@ * Now `--mode check` fails on missing trailing blank lines. [Issue   743](https://github.com/tweag/ormolu/issues/743). -* Fixed indentation of arrow forms in do blocks. [Issue+* Fix indentation of arrow forms in do blocks. [Issue   739](https://github.com/tweag/ormolu/issues/739).  #### Ormolu 0.1.4.1@@ -735,7 +922,7 @@ * Added support for monad comprehensions. [Issue   665](https://github.com/tweag/ormolu/issues/665). -* Fixed a bug when a space was inserted in front of promoted types even when+* Fix a bug when a space was inserted in front of promoted types even when   it wasn't strictly necessary. [Issue   668](https://github.com/tweag/ormolu/issues/668). @@ -745,7 +932,7 @@  #### Ormolu 0.1.3.1 -* Fixed a problem with multiline record updates using the record dot+* Fix a problem with multiline record updates using the record dot   preprocessor. [Issue 658](https://github.com/tweag/ormolu/issues/658).  ## Fourmolu 0.3.0.0@@ -780,7 +967,7 @@  #### Ormolu 0.1.2.0: -* Fixed the bug when comments in different styles got glued together after+* Fix the bug when comments in different styles got glued together after   formatting. [Issue 589](https://github.com/tweag/ormolu/issues/589).  * Added `-i` as a shortcut for `--mode inplace`. [Issue@@ -809,26 +996,26 @@ * Improved sorting of operators in imports. [Issue   602](https://github.com/tweag/ormolu/issues/602). -* Fixed a bug related to trailing space in multiline comments in certain+* Fix a bug related to trailing space in multiline comments in certain   cases. [Issue 603](https://github.com/tweag/ormolu/issues/602).  * Added support for formatting linked lists with `(:)` as line terminator.   [Issue 478](https://github.com/tweag/ormolu/issues/478). -* Fixed rendering of function arguments in multiline layout. [Issue+* Fix rendering of function arguments in multiline layout. [Issue   609](https://github.com/tweag/ormolu/issues/609).  * Blank lines between definitions in `let` and `while` bindings are now   preserved. [Issue 554](https://github.com/tweag/ormolu/issues/554). -* Fixed the bug when type applications stuck to the `$` of TH splices that+* Fix the bug when type applications stuck to the `$` of TH splices that   followed them. [Issue 613](https://github.com/tweag/ormolu/issues/613).  * Improved region formatting so that indented fragments—such as definitions   inside of `where` clauses—can be formatted. [Issue   572](https://github.com/tweag/ormolu/issues/572). -* Fixed the bug related to the de-association of pragma comments. [Issue+* Fix the bug related to the de-association of pragma comments. [Issue   619](https://github.com/tweag/ormolu/issues/619).  ## Fourmolu 0.1.0.0@@ -841,24 +1028,24 @@  #### Ormolu 0.1.0.0: -* Fixed rendering of type signatures concerning several identifiers. [Issue+* Fix rendering of type signatures concerning several identifiers. [Issue   566](https://github.com/tweag/ormolu/issues/566). -* Fixed an idempotence issue with inline comments in tuples and parentheses.+* Fix an idempotence issue with inline comments in tuples and parentheses.   [Issue 450](https://github.com/tweag/ormolu/issues/450). -* Fixed an idempotence issue when certain comments were picked up as+* Fix an idempotence issue when certain comments were picked up as   “continuation” of a series of comments [Issue   449](https://github.com/tweag/ormolu/issues/449). -* Fixed an idempotence issue related to different indentation levels in a+* Fix an idempotence issue related to different indentation levels in a   comment series. [Issue 512](https://github.com/tweag/ormolu/issues/512). -* Fixed an idempotence issue related to comments which may happen to be+* Fix an idempotence issue related to comments which may happen to be   separated from the elements they are attached to by the equality sign.   [Issue 340](https://github.com/tweag/ormolu/issues/340). -* Fixed an idempotence issue with type synonym and data declarations where+* Fix an idempotence issue with type synonym and data declarations where   the type has a Haddock. [Issue   578](https://github.com/tweag/ormolu/issues/578). @@ -866,17 +1053,17 @@   multiple blank lines in a row. [Issue   518](https://github.com/tweag/ormolu/issues/518). -* Fixed rendering of comments around if expressions. [Issue+* Fix rendering of comments around if expressions. [Issue   458](https://github.com/tweag/ormolu/issues/458).  * Unnamed fields of data constructors are now documented using the `-- ^`   syntax. [Issue 445](https://github.com/tweag/ormolu/issues/445) and [Issue   428](https://github.com/tweag/ormolu/issues/428). -* Fixed non-idempotent transformation of partly documented data definition.+* Fix non-idempotent transformation of partly documented data definition.   [Issue 590](https://github.com/tweag/ormolu/issues/590). -* Fixed an idempotence issue related to operators. [Issue+* Fix an idempotence issue related to operators. [Issue   522](https://github.com/tweag/ormolu/issues/522).  * Renamed the `--check-idempotency` flag to `--check-idempotence`.@@ -907,7 +1094,7 @@   select a region to format. [Issue   516](https://github.com/tweag/ormolu/issues/516). -* Fixed rendering of module headers in the presence of preceding comments or+* Fix rendering of module headers in the presence of preceding comments or   Haddocks. [Issue 561](https://github.com/tweag/ormolu/issues/561).  ## Ormolu 0.0.4.0@@ -928,7 +1115,7 @@   now put on its own line. [Issue   509](https://github.com/tweag/ormolu/issues/509). -* Fixed the bug pertaining to rendering of arrow notation with multiline+* Fix the bug pertaining to rendering of arrow notation with multiline   expressions. [Issue 513](https://github.com/tweag/ormolu/issues/513).  * Made rendering of data type definitions, value-level applications, and@@ -946,15 +1133,15 @@  ## Ormolu 0.0.3.1 -* Fixed rendering of record updates with the record dot preprocessor syntax+* Fix rendering of record updates with the record dot preprocessor syntax   [Issue 498](https://github.com/tweag/ormolu/issues/498).  ## Ormolu 0.0.3.0 -* Fixed an issue related to unnecessary use of curly braces. [Issue+* Fix an issue related to unnecessary use of curly braces. [Issue   473](https://github.com/tweag/ormolu/issues/473). -* Fixed the issue with formatting multi-way if when it happens to be a+* Fix the issue with formatting multi-way if when it happens to be a   function applied to arguments [Issue   488](https://github.com/tweag/ormolu/issues/488). This changed the way   multi-line if is formatted in general.@@ -966,7 +1153,7 @@   potentially-hanging consturctions in the presence of comments. [Issue   447](https://github.com/tweag/ormolu/issues/447). -* Fixed indentation in presence of type applications. [Issue+* Fix indentation in presence of type applications. [Issue   493](https://github.com/tweag/ormolu/issues/493).  * Class and instance declarations now do not have a blank line after@@ -984,20 +1171,20 @@ * Now unrecognized GHC options passed with `--ghc-opt` cause Ormolu to fail   (exit code 7). -* Fixed formatting of result type in closed type families. See [issue+* Fix formatting of result type in closed type families. See [issue   420](https://github.com/tweag/ormolu/issues/420). -* Fixed a minor inconsistency between formatting of normal and foreign type+* Fix a minor inconsistency between formatting of normal and foreign type   signatures. See [issue 408](https://github.com/tweag/ormolu/issues/408). -* Fixed a bug when comment before module header with Haddock was moved+* Fix a bug when comment before module header with Haddock was moved   inside the export list. See [issue   430](https://github.com/tweag/ormolu/issues/430).  * Empty `forall`s are now correctly preserved. See [issue   429](https://github.com/tweag/ormolu/issues/429). -* Fixed [issue 446](https://github.com/tweag/ormolu/issues/446), which+* Fix [issue 446](https://github.com/tweag/ormolu/issues/446), which   involved braces and operators.  * When there are comments between preceding Haddock (pipe-style) and its@@ -1017,7 +1204,7 @@ * Sorting language pragmas cannot not change meaning of the input program   anymore. [Issue 404](https://github.com/tweag/ormolu/issues/404). -* Fixed formatting of applications where function is a complex expression.+* Fix formatting of applications where function is a complex expression.   [Issue 444](https://github.com/tweag/ormolu/issues/444).  ## Ormolu 0.0.1.0
LICENSE.md view
@@ -12,7 +12,7 @@   notice, this list of conditions and the following disclaimer in the   documentation and/or other materials provided with the distribution. -* Neither the name Tweag I/O nor the names of contributors may be used to+* Neither the names Tweag I/O and Mark Karpov nor the names of contributors may be used to   endorse or promote products derived from this software without specific   prior written permission. 
README.md view
@@ -26,20 +26,18 @@  We share all bar one of Ormolu's goals: -* Using GHC's own parser to avoid parsing problems caused by+* Use GHC's own parser to avoid the parsing problems caused by   [`haskell-src-exts`](https://hackage.haskell.org/package/haskell-src-exts).-* Let some whitespace be programmable. The layout of the input influences-  the layout choices in the output. This means that the choices between-  single-line/multi-line layouts in certain situations are made by the user,-  not by an algorithm. This makes the implementation simpler and leaves some-  control to the user while still guaranteeing that the formatted code is-  stylistically consistent.-* Writing code in such a way so it's easy to modify and maintain.-* That formatting style aims to result in minimal diffs.+* Make some whitespace programmable. The layout of the input influences the+  layout choices in the output, so the choice between single-line and+  multi-line layouts is made by the user rather than by an algorithm. This+  keeps the implementation simpler and leaves some control to the user while+  still guaranteeing that the formatted code is stylistically consistent.+* Produce minimal diffs. * Choose a style compatible with modern dialects of Haskell. As new Haskell-  extensions enter broad use, we may change the style to accommodate them.-* Idempotence: formatting already formatted code doesn't change it.-* Be well-tested and robust so that the formatter can be used in large+  extensions enter broad use, we may adjust the style to accommodate them.+* Guarantee idempotence: formatting already formatted code doesn't change it.+* Stay well-tested and robust, so that the formatter can be used in large   projects. * ~~Implementing one “true” formatting style which admits no configuration.~~ We allow configuration of various parameters, via CLI options or config files. We encourage any contributions which add further flexibility. @@ -49,13 +47,24 @@  ## Installation -To install the latest release from Hackage, simply install with Cabal or Stack:+### (Recommended) Install with GHCup -```console-$ cabal install fourmolu-$ stack install fourmolu+```bash+ghcup config add-release-channel 3rdparty+ghcup install fourmolu latest ``` +### Install with Cabal/Stack++```bash+cabal install fourmolu+stack install fourmolu+```++### Install with [dotslash](https://dotslash-cli.com/docs/)++Copy the configuration in the GitHub release notes.+ ## Building from source  ```console@@ -67,13 +76,13 @@  ## Usage -The following will print the formatted output to the standard output.+The following prints the formatted output to the standard output:  ```console $ fourmolu Module.hs ``` -Add `-i` (or `--mode inplace`) to replace the contents of the input file with the formatted output.+Add `-i` (or `--mode inplace`) to replace the contents of the input file with the formatted output:  ```console $ fourmolu -i Module.hs@@ -93,7 +102,7 @@ $ git ls-files -z '*.hs' | xargs -P 12 -0 fourmolu --mode inplace ``` -To check if files are already formatted (useful on CI):+To check whether files are already formatted (useful on CI):  ```console $ fourmolu --mode check src@@ -115,19 +124,20 @@  Fourmolu can be integrated with your editor via the [Haskell Language Server](https://haskell-language-server.readthedocs.io/en/latest/index.html). Just set `haskell.formattingProvider` to `fourmolu` ([instructions](https://haskell-language-server.readthedocs.io/en/latest/configuration.html#language-specific-server-options)). -### GitHub actions+### GitHub Actions -[`run-fourmolu`](https://github.com/haskell-actions/run-fourmolu) is the recommended way to ensure that a project is formatted with Fourmolu.+[`run-fourmolu`](https://github.com/haskell-actions/run-fourmolu) is the recommended way to ensure that a project stays formatted with Fourmolu.  ### Language extensions, dependencies, and fixities  Fourmolu automatically locates the Cabal file that corresponds to a given-source code file. Cabal files are used to extract both default extensions-and dependencies. Default extensions directly affect behavior of the GHC-parser, while dependencies are used to figure out fixities of operators that-appear in the source code. Fixities can also be overridden via the `fixities` configuration option in `fourmolu.yaml`. When the input comes from-stdin, one can pass `--stdin-input-file` which will give Fourmolu the location-that should be used as the starting point for searching for `.cabal` files.+source file. Cabal files are used to extract both default extensions and+dependencies. Default extensions directly affect the behavior of the GHC+parser, while dependencies are used to determine the fixities of operators+that appear in the source code. Fixities can also be overridden via the `fixities` configuration option in+`fourmolu.yaml`. When the input comes from stdin, you+can pass `--stdin-input-file` to tell Fourmolu which location to use as the+starting point when searching for `.cabal` files.  Here is an example of the `fixities` configuration: @@ -145,21 +155,20 @@   - infixr 3.7 <~> ``` -It uses exactly the same syntax as usual Haskell fixity declarations to make-it easier for Haskellers to edit and maintain. Since Ormolu 0.7.8.0-fractional precedences are supported for more precise control over-formatting of complex operator chains.+It uses exactly the same syntax as ordinary Haskell fixity declarations,+which makes it easier for Haskellers to edit and maintain. Since Ormolu+0.7.8.0, fractional precedences are supported for more precise control over+the formatting of complex operator chains.  `fourmolu.yaml` can also contain instructions about-module re-exports that Fourmolu should be aware of. This might be desirable-because at the moment Fourmolu cannot know about all possible module-re-exports in the ecosystem and only few of them are actually important when-it comes to fixity deduction. In 99% of cases the user won't have to do-anything, especially since most common re-exports are already programmed-into Fourmolu. (You are welcome to open PRs to make Fourmolu aware of more-re-exports by default.) However, when the fixity of an operator is not-inferred correctly, making Fourmolu aware of a re-export may come in handy.-Here is an example:+module re-exports that Fourmolu should be aware of. This can be useful because+Fourmolu cannot know about every possible module re-export in the ecosystem,+and only a few of them actually matter for fixity deduction. In 99% of cases+you won't have to do anything, especially since the most common re-exports+are already built into Fourmolu. (You are welcome to open PRs upstream to Ormolu to make Fourmolu+aware of more re-exports by default.) However, when the fixity of an operator+is not inferred correctly, making Fourmolu aware of a re-export may help. Here+is an example:  ```yaml reexports:@@ -194,22 +203,22 @@ {- FOURMOLU_ENABLE -} ``` -This allows us to disable formatting selectively for code between these-markers or disable it for the entire file. To achieve the latter, just put-`{- FOURMOLU_DISABLE -}` at the very top. Note that for Fourmolu to work the-fragments where Fourmolu is enabled must be parseable on their own. Because of-that the magic comments cannot be placed arbitrarily, but rather must-enclose independent top-level definitions.+These let you disable formatting selectively for the code between the two+markers, or for the entire file. To disable formatting for the whole file,+just put `{- FOURMOLU_DISABLE -}` at the very top. Note that the fragments+where Fourmolu is enabled must be parseable on their own. Because of this, the+magic comments cannot be placed arbitrarily; they must enclose independent+top-level definitions.  `{- ORMOLU_DISABLE -}` and `{- ORMOLU_ENABLE -}`, respectively, can be used to the same effect, and the two styles of magic comments can be mixed.  ### Regions -One can ask Fourmolu to format a region of input and leave the rest-unformatted. This is accomplished by passing the `--start-line` and-`--end-line` command line options. `--start-line` defaults to the beginning-of the file, while `--end-line` defaults to the end.+You can ask Fourmolu to format a region of the input and leave the rest+unformatted by passing the `--start-line` and `--end-line` command line+options. `--start-line` defaults to the beginning of the file, and+`--end-line` defaults to the end.  Note that the selected region needs to be parseable Haskell code on its own. @@ -228,6 +237,7 @@ 8         | Cabal file parsing failed 9         | Missing input file path when using stdin input and accounting for .cabal files 10        | Parse error while parsing fixity overrides+11        | Comments of original and formatted code differ 100       | In checking mode: unformatted files 101       | Inplace mode does not work with stdin 102       | Other issue (with multiple input files)@@ -235,10 +245,10 @@  ### Using as a library -The `fourmolu` package can also be depended upon from other Haskell programs.-For these purposes only the top `Ormolu` module should be considered stable.-It follows [PVP](https://pvp.haskell.org/) starting from the version-0.10.2.0. Rely on other modules at your own risk.+The `fourmolu` package can also be used as a dependency from other Haskell+programs. For this purpose, only the top-level `Ormolu` module should be+considered stable. It follows the [PVP](https://pvp.haskell.org/) starting+from version 0.5.3.0. Rely on other modules at your own risk.  ## Troubleshooting @@ -253,25 +263,26 @@   specify the correct fixities in a `fourmolu.yaml` file.  * If this is a third-party operator (e.g. from `base` or some other package-  from Hackage), Ormolu probably doesn't recognize that the operator is the+  on Hackage), Ormolu probably doesn't recognize that the operator is the   same as the third-party one. -  Some reasons this might be the case:+  Some possible reasons for this: -    * You might have a custom Prelude that re-exports things from Prelude-    * You might have `-XNoImplicitPrelude` turned on+    * You have a custom Prelude that re-exports things from the standard+      Prelude.+    * You have `-XNoImplicitPrelude` turned on. -  If any of these are true, make sure to specify the reexports correctly in-  a `fourmolu.yaml` file.+  If either of these applies, make sure to specify the re-exports correctly+  in a `fourmolu.yaml` file. -You can see how Ormolu decides the fixity of operators if you use `--debug`.+You can see how Ormolu decides the fixity of operators by using `--debug`.  ## Limitations  * CPP support is experimental. CPP is virtually impossible to handle-  correctly, so we process them as a sort of unchangeable snippets. This-  works only in simple cases when CPP conditionals surround top-level-  declarations. See the [CPP](https://github.com/tweag/ormolu/blob/master/DESIGN.md#cpp) section in the design notes for a+  correctly, so Fourmolu treats CPP sections as unchangeable snippets. This+  works only in simple cases, where CPP conditionals surround top-level+  declarations. See the [CPP][design-cpp] section of the design notes for a   discussion of the dangers. * Various minor idempotence issues, most of them are related to comments or column limits. 
app/Main.hs view
@@ -220,7 +220,7 @@           InPlace -> do             hPutStrLn               stderr-              "In place editing is not supported when input comes from stdin."+              "In-place editing is not supported when the input comes from stdin."             -- 101 is different from all the other exit codes we already use.             return (ExitFailure 101)           Check -> do@@ -284,7 +284,7 @@         Nothing -> return ExitSuccess         Just diff -> do           runTerm (printTextDiff diff) (cfgColorMode rawConfig) stderr-          -- 100 is different to all the other exit code that are emitted+          -- 100 is different from all the other exit codes that are emitted           -- either from an 'OrmoluException' or from 'error' and           -- 'notImplemented'.           return (ExitFailure 100)@@ -468,7 +468,7 @@         help "Fail if formatting is not idempotent"       ]     -- We cannot parse the source type here, because we might need to do-    -- autodection based on the input file extension (not available here)+    -- autodetection based on the input file extension (not available here)     -- before storing the resolved value in the config struct.     <*> pure ModuleSource     <*> (option parseColorMode . mconcat)@@ -492,7 +492,6 @@     <*> pure defaultPrinterOpts -- unused; overwritten in resolveConfig     <*> (fmap Set.fromList . many . strOption . mconcat)       [ long "local-modules",-        short 'm',         metavar "LOCAL_MODULES",         help "Modules Fourmolu should consider as local by the current Cabal package"       ]
+ data/examples/declaration/data/comment-in-empty-record-four-out.hs view
@@ -0,0 +1,6 @@+instance StateKey ExampleReq where+    data State ExampleReq = ExampleState+        {+        -- in here you can put any state that the+        -- run.+        }
+ data/examples/declaration/data/comment-in-empty-record-out.hs view
@@ -0,0 +1,6 @@+instance StateKey ExampleReq where+  data State ExampleReq = ExampleState+    {+    -- in here you can put any state that the+    -- run.+    }
+ data/examples/declaration/data/comment-in-empty-record.hs view
@@ -0,0 +1,5 @@+instance StateKey ExampleReq where+  data State ExampleReq = ExampleState {+        -- in here you can put any state that the+        -- run.+        }
+ data/examples/declaration/data/haddock-before-deriving-four-out.hs view
@@ -0,0 +1,5 @@+data A = A+    deriving+        ( -- | B+          Eq+        )
+ data/examples/declaration/data/haddock-before-deriving-out.hs view
@@ -0,0 +1,5 @@+data A = A+  deriving+    ( -- | B+      Eq+    )
+ data/examples/declaration/data/haddock-before-deriving.hs view
@@ -0,0 +1,3 @@+data A = A+  -- | B+  deriving (Eq)
+ data/examples/declaration/data/haddock-before-record-braces-four-out.hs view
@@ -0,0 +1,6 @@+module Example where++data Hello = Hello+    { hello :: String+    -- ^ hello world+    }
+ data/examples/declaration/data/haddock-before-record-braces-out.hs view
@@ -0,0 +1,6 @@+module Example where++data Hello = Hello+  { -- | hello world+    hello :: String+  }
+ data/examples/declaration/data/haddock-before-record-braces.hs view
@@ -0,0 +1,5 @@+module Example where++data Hello = Hello+  -- | hello world+  {hello :: String}
data/examples/declaration/data/infix-haddocks-out.hs view
@@ -24,8 +24,8 @@  data DocPartial   = Left -- ^ left docs-    -- on multiple-    -- lines+         -- on multiple+         -- lines       :*:       Right   | -- | op
+ data/examples/declaration/data/linear-four-out.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE LinearTypes #-}++data Record = Rec {x %'Many :: Int, y :: Char}++data T2 a b c where+    MkT2 :: a -> b %1 -> c %1 -> T2 a b c++data T2 a b c = MkT2 {x %Many :: a, y :: b, z :: c}++data T3 a m where+    MkT3 :: a %m -> T3 a m
+ data/examples/declaration/data/linear-out.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE LinearTypes #-}++data Record = Rec {x %'Many :: Int, y :: Char}++data T2 a b c where+  MkT2 :: a -> b %1 -> c %1 -> T2 a b c++data T2 a b c = MkT2 {x %Many :: a, y :: b, z :: c}++data T3 a m where+  MkT3 :: a %m -> T3 a m
+ data/examples/declaration/data/linear.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE LinearTypes #-}+data Record = Rec { x %'Many :: Int, y :: Char }++data T2 a b c where+    MkT2 :: a -> b %1 -> c %1 -> T2 a b c++data T2 a b c = MkT2 { x %Many :: a, y :: b, z :: c }++data T3 a m where+    MkT3 :: a %m -> T3 a m
+ data/examples/declaration/data/record-empty-haddock-four-out.hs view
@@ -0,0 +1,5 @@+data A = A+    { -- \|+      --+      a :: Int+    }
+ data/examples/declaration/data/record-empty-haddock-out.hs view
@@ -0,0 +1,5 @@+data A = A+  { -- \|+    --+    a :: Int+  }
+ data/examples/declaration/data/record-empty-haddock.hs view
@@ -0,0 +1,6 @@+data A = A+  {+    -- |+    -- +    a :: Int+  }
data/examples/declaration/data/record-four-out.hs view
@@ -11,7 +11,7 @@     , fooGag       , fooGog ::         NonEmpty-            ( Indentity+            ( Identity                 Bool             )     -- ^ GagGog
data/examples/declaration/data/record-out.hs view
@@ -12,7 +12,7 @@     fooGag,     fooGog ::       NonEmpty-        ( Indentity+        ( Identity             Bool         ),     -- | Huh!
data/examples/declaration/data/record.hs view
@@ -6,7 +6,7 @@   { fooX :: Int -- ^ X   , fooY :: Int -- ^ Y   , fooBar, fooBaz :: NonEmpty (Identity Bool) -- ^ BarBaz-  , fooGag, fooGog :: NonEmpty (Indentity+  , fooGag, fooGog :: NonEmpty (Identity                                   Bool)     -- ^ GagGog   , fooFoo
+ data/examples/declaration/data/required-type-arguments-four-out.hs view
@@ -0,0 +1,25 @@+data T a where+    Typed :: forall a -> a -> T a++f1 (Typed a x) = x :: a+f2 (Typed Int n) = n * 2+f3 (Typed ((->) w Bool) g) = not . g++data D x where+    MkD1 ::+        forall a b ->+        a ->+        b ->+        D (a, b)+    MkD2 ::+        forall a.+        forall b ->+        a ->+        b ->+        D (a, b)+    MkD3 ::+        forall a ->+        a ->+        forall b ->+        b ->+        D (a, b)
+ data/examples/declaration/data/required-type-arguments-out.hs view
@@ -0,0 +1,27 @@+data T a where+  Typed :: forall a -> a -> T a++f1 (Typed a x) = x :: a++f2 (Typed Int n) = n * 2++f3 (Typed ((->) w Bool) g) = not . g++data D x where+  MkD1 ::+    forall a b ->+    a ->+    b ->+    D (a, b)+  MkD2 ::+    forall a.+    forall b ->+    a ->+    b ->+    D (a, b)+  MkD3 ::+    forall a ->+    a ->+    forall b ->+    b ->+    D (a, b)
+ data/examples/declaration/data/required-type-arguments.hs view
@@ -0,0 +1,24 @@+data T a where+  Typed :: forall a -> a -> T a++f1 (Typed a x) = x :: a+f2 (Typed Int n) = n*2+f3 (Typed ((->) w Bool) g) = not . g++data D x where+  MkD1 :: forall a b ->+          a ->+          b ->+          D (a, b)++  MkD2 :: forall a.+          forall b ->+          a ->+          b ->+          D (a, b)++  MkD3 :: forall a ->+          a ->+          forall b ->+          b ->+          D (a, b)
data/examples/declaration/data/unnamed-field-comment-3-out.hs view
@@ -1,5 +1,1 @@-data A-  = A-      -- | a number-      Int-      Bool+data A = A {- | a number -} Int Bool
+ data/examples/declaration/data/unpack-field-comment-0-four-out.hs view
@@ -0,0 +1,4 @@+data Buffer+    = Buffer+        {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+        {-# UNPACK #-} !(Ptr Word8) -- beginning of slice
+ data/examples/declaration/data/unpack-field-comment-0-out.hs view
@@ -0,0 +1,4 @@+data Buffer+  = Buffer+      {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+      {-# UNPACK #-} !(Ptr Word8) -- beginning of slice
+ data/examples/declaration/data/unpack-field-comment-0.hs view
@@ -0,0 +1,2 @@+data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+                     {-# UNPACK #-} !(Ptr Word8)        -- beginning of slice
+ data/examples/declaration/data/unpack-field-comment-1-four-out.hs view
@@ -0,0 +1,4 @@+data P+    = P+        {-# UNPACK #-} !Word32 -- left word+        {-# UNPACK #-} !Word32 -- right word
+ data/examples/declaration/data/unpack-field-comment-1-out.hs view
@@ -0,0 +1,4 @@+data P+  = P+      {-# UNPACK #-} !Word32 -- left word+      {-# UNPACK #-} !Word32 -- right word
+ data/examples/declaration/data/unpack-field-comment-1.hs view
@@ -0,0 +1,2 @@+data P = P {-# UNPACK #-} !Word32 -- left word+           {-# UNPACK #-} !Word32 -- right word
+ data/examples/declaration/data/unpack-field-comment-2-four-out.hs view
@@ -0,0 +1,4 @@+data TBQueue a+    = TBQueue+        {-# UNPACK #-} !(TVar Natural) -- CR:  read capacity+        {-# UNPACK #-} !(TVar [a]) -- R:   elements waiting to be read
+ data/examples/declaration/data/unpack-field-comment-2-out.hs view
@@ -0,0 +1,4 @@+data TBQueue a+  = TBQueue+      {-# UNPACK #-} !(TVar Natural) -- CR:  read capacity+      {-# UNPACK #-} !(TVar [a]) -- R:   elements waiting to be read
+ data/examples/declaration/data/unpack-field-comment-2.hs view
@@ -0,0 +1,3 @@+data TBQueue a+   = TBQueue {-# UNPACK #-} !(TVar Natural) -- CR:  read capacity+             {-# UNPACK #-} !(TVar [a])     -- R:   elements waiting to be read
+ data/examples/declaration/data/unpack-field-comment-3-four-out.hs view
@@ -0,0 +1,4 @@+data Builder+    = Builder+        {-# UNPACK #-} !Int -- offset+        {-# UNPACK #-} !Int -- used units
+ data/examples/declaration/data/unpack-field-comment-3-out.hs view
@@ -0,0 +1,4 @@+data Builder+  = Builder+      {-# UNPACK #-} !Int -- offset+      {-# UNPACK #-} !Int -- used units
+ data/examples/declaration/data/unpack-field-comment-3.hs view
@@ -0,0 +1,3 @@+data Builder = Builder+     {-# UNPACK #-} !Int -- offset+     {-# UNPACK #-} !Int -- used units
+ data/examples/declaration/foreign/foreign-import-multiline-four-out.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE MultilineStrings #-}++foreign import capi+    """+    foo+       bar+    """+    foo :: Int -> Int
+ data/examples/declaration/foreign/foreign-import-multiline-out.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE MultilineStrings #-}++foreign import capi+  """+  foo+     bar+  """+  foo :: Int -> Int
+ data/examples/declaration/foreign/foreign-import-multiline.hs view
@@ -0,0 +1,6 @@+{-# language MultilineStrings #-}++foreign import capi """+         foo+            bar+     """ foo :: Int -> Int
data/examples/declaration/rewrite-rule/prelude2-four-out.hs view
@@ -11,7 +11,7 @@ -- when we disable the rule that expands (++) into foldr  -- The foldr/cons rule looks nice, but it can give disastrously--- bloated code when commpiling+-- bloated code when compiling --      array (a,b) [(1,2), (2,2), (3,2), ...very long list... ] -- i.e. when there are very very long literal lists -- So I've disabled it for now. We could have special cases
data/examples/declaration/rewrite-rule/prelude2-out.hs view
@@ -11,7 +11,7 @@ -- when we disable the rule that expands (++) into foldr  -- The foldr/cons rule looks nice, but it can give disastrously--- bloated code when commpiling+-- bloated code when compiling --      array (a,b) [(1,2), (2,2), (3,2), ...very long list... ] -- i.e. when there are very very long literal lists -- So I've disabled it for now. We could have special cases
data/examples/declaration/rewrite-rule/prelude2.hs view
@@ -11,7 +11,7 @@         -- when we disable the rule that expands (++) into foldr  -- The foldr/cons rule looks nice, but it can give disastrously--- bloated code when commpiling+-- bloated code when compiling --      array (a,b) [(1,2), (2,2), (3,2), ...very long list... ] -- i.e. when there are very very long literal lists -- So I've disabled it for now. We could have special cases
data/examples/declaration/rewrite-rule/prelude4-four-out.hs view
@@ -2,6 +2,7 @@ "unpack" [~1] forall a. unpackCString # a = build (unpackFoldrCString # a) "unpack-list" [1] forall a. unpackFoldrCString # a (:) [] = unpackCString # a "unpack-append" forall a n. unpackFoldrCString # a (:) n = unpackAppendCString # a n+ -- There's a built-in rule (in PrelRules.lhs) for --      unpackFoldr "foo" c (unpackFoldr "baz" c n)  =  unpackFoldr "foobaz" c n     #-}
data/examples/declaration/rewrite-rule/prelude4-out.hs view
@@ -2,6 +2,7 @@ "unpack" [~1] forall a. unpackCString # a = build (unpackFoldrCString # a) "unpack-list" [1] forall a. unpackFoldrCString # a (:) [] = unpackCString # a "unpack-append" forall a n. unpackFoldrCString # a (:) n = unpackAppendCString # a n+ -- There's a built-in rule (in PrelRules.lhs) for --      unpackFoldr "foo" c (unpackFoldr "baz" c n)  =  unpackFoldr "foobaz" c n   #-}
+ data/examples/declaration/signature/specialize/specialize-2-four-out.hs view
@@ -0,0 +1,8 @@+{-# SPECIALIZE addMult @Double #-}+{-# SPECIALIZE addMult (5 :: Int) #-}+{-# SPECIALIZE addMult 5 :: Int -> Int #-}++{-# SPECIALIZE [1] forall x y. f @Int True (x, y) #-}++{-# SPECIALIZE forall x xs. loop (x : xs)+    #-}
+ data/examples/declaration/signature/specialize/specialize-2-out.hs view
@@ -0,0 +1,8 @@+{-# SPECIALIZE addMult @Double #-}+{-# SPECIALIZE addMult (5 :: Int) #-}+{-# SPECIALIZE addMult 5 :: Int -> Int #-}++{-# SPECIALIZE [1] forall x y. f @Int True (x, y) #-}++{-# SPECIALIZE forall x xs. loop (x : xs)+  #-}
+ data/examples/declaration/signature/specialize/specialize-2.hs view
@@ -0,0 +1,9 @@+{-# SPECIALISE addMult @Double #-}+{-# SPECIALISE addMult (5 :: Int) #-}+{-# SPECIALISE addMult 5 :: Int -> Int #-}++{-# SPECIALISE [1] forall x y. f @Int True (x,y) #-}++{-# SPECIALISE+  forall x xs .+  loop (x:xs) #-}
+ data/examples/declaration/signature/specialize/specialize-3-four-out.hs view
@@ -0,0 +1,10 @@+sep, fsep, hsep :: (Applicative m, Foldable t) => t (m Doc) -> m Doc+sep = fmap P.sep . sequenceAFoldable+{-# SPECIALIZE NOINLINE sep :: [TCM Doc] -> TCM Doc #-}+{-# SPECIALIZE NOINLINE sep :: List1 (TCM Doc) -> TCM Doc #-}+fsep = fmap P.fsep . sequenceAFoldable+{-# SPECIALIZE NOINLINE [2] fsep :: [TCM Doc] -> TCM Doc #-}+{-# SPECIALIZE NOINLINE [2] fsep :: List1 (TCM Doc) -> TCM Doc #-}+hsep = fmap P.hsep . sequenceAFoldable+{-# SPECIALIZE NOINLINE [~2] hsep :: [TCM Doc] -> TCM Doc #-}+{-# SPECIALIZE NOINLINE [~2] hsep :: List1 (TCM Doc) -> TCM Doc #-}
+ data/examples/declaration/signature/specialize/specialize-3-out.hs view
@@ -0,0 +1,10 @@+sep, fsep, hsep :: (Applicative m, Foldable t) => t (m Doc) -> m Doc+sep = fmap P.sep . sequenceAFoldable+{-# SPECIALIZE NOINLINE sep :: [TCM Doc] -> TCM Doc #-}+{-# SPECIALIZE NOINLINE sep :: List1 (TCM Doc) -> TCM Doc #-}+fsep = fmap P.fsep . sequenceAFoldable+{-# SPECIALIZE NOINLINE [2] fsep :: [TCM Doc] -> TCM Doc #-}+{-# SPECIALIZE NOINLINE [2] fsep :: List1 (TCM Doc) -> TCM Doc #-}+hsep = fmap P.hsep . sequenceAFoldable+{-# SPECIALIZE NOINLINE [~2] hsep :: [TCM Doc] -> TCM Doc #-}+{-# SPECIALIZE NOINLINE [~2] hsep :: List1 (TCM Doc) -> TCM Doc #-}
+ data/examples/declaration/signature/specialize/specialize-3.hs view
@@ -0,0 +1,4 @@+sep, fsep, hsep :: (Applicative m, Foldable t) => t (m Doc) -> m Doc+sep  = fmap P.sep  . sequenceAFoldable  ; {-# SPECIALIZE NOINLINE sep  :: [TCM Doc] -> TCM Doc #-} ; {-# SPECIALIZE NOINLINE sep  :: List1 (TCM Doc) -> TCM Doc #-}+fsep = fmap P.fsep . sequenceAFoldable  ; {-# SPECIALIZE NOINLINE [2] fsep :: [TCM Doc] -> TCM Doc #-} ; {-# SPECIALIZE NOINLINE [2] fsep :: List1 (TCM Doc) -> TCM Doc #-}+hsep = fmap P.hsep . sequenceAFoldable  ; {-# SPECIALIZE NOINLINE [~2] hsep :: [TCM Doc] -> TCM Doc #-} ; {-# SPECIALIZE NOINLINE [~2] hsep :: List1 (TCM Doc) -> TCM Doc #-}
data/examples/declaration/type-families/closed-type-family/multi-line-four-out.hs view
@@ -23,6 +23,5 @@     F a = String  type family F a where-    F a -- foo-        =+    F a = -- foo         a
data/examples/declaration/type-families/closed-type-family/multi-line-out.hs view
@@ -25,6 +25,5 @@   F a = String  type family F a where-  F a -- foo-    =+  F a = -- foo     a
data/examples/declaration/type-synonyms/multi-line-four-out.hs view
@@ -19,6 +19,5 @@         :<|> "route2" :> ApiRoute2 -- comment here         :<|> OmitDocs :> "i" :> ASomething API -type A -- foo-    =+type A = -- foo     B
data/examples/declaration/type-synonyms/multi-line-out.hs view
@@ -19,6 +19,5 @@     :<|> "route2" :> ApiRoute2 -- comment here     :<|> OmitDocs :> "i" :> ASomething API -type A -- foo-  =+type A = -- foo   B
data/examples/declaration/value/function/arrow/proc-do-complex-four-out.hs view
@@ -12,7 +12,7 @@                 )         -> do             -- Begin do-            (x, y) <- -- GHC parser fails if layed out over multiple lines+            (x, y) <- -- GHC parser fails if laid out over multiple lines                 f -- Call into f                     ( a                     , c -- Tuple together arguments@@ -29,8 +29,7 @@                     Left                         ( z                             , w-                            ) -> \u ->-                            -- Procs can have lambdas+                            ) -> \u -> -- Procs can have lambdas                             let                                 v =                                     u -- Actually never used
data/examples/declaration/value/function/arrow/proc-do-complex-out.hs view
@@ -12,7 +12,7 @@         )     -> do       -- Begin do-      (x, y) <- -- GHC parser fails if layed out over multiple lines+      (x, y) <- -- GHC parser fails if laid out over multiple lines         f -- Call into f           ( a,             c -- Tuple together arguments@@ -29,8 +29,7 @@           Left             ( z,               w-              ) -> \u ->-              -- Procs can have lambdas+              ) -> \u -> -- Procs can have lambdas               let v =                     u -- Actually never used                       ^ 2
data/examples/declaration/value/function/arrow/proc-do-complex.hs view
@@ -9,7 +9,7 @@         (e, f)       ) ->     do -- Begin do-        (x,y) -- GHC parser fails if layed out over multiple lines+        (x,y) -- GHC parser fails if laid out over multiple lines          <- f -- Call into f               (a,                c) -- Tuple together arguments
data/examples/declaration/value/function/arrow/proc-lambdas-four-out.hs view
@@ -5,6 +5,5 @@ bar =     proc x -> \f g h ->         \() ->-            \(Left (x, y)) ->-                -- Tuple value+            \(Left (x, y)) -> -- Tuple value                 f (g (h x)) -< y
data/examples/declaration/value/function/arrow/proc-lambdas-out.hs view
@@ -5,6 +5,5 @@ bar =   proc x -> \f g h ->     \() ->-      \(Left (x, y)) ->-        -- Tuple value+      \(Left (x, y)) -> -- Tuple value         f (g (h x)) -< y
data/examples/declaration/value/function/awkward-comment-0-four-out.hs view
@@ -1,6 +1,5 @@ mergeErrorReply :: ParseError -> Reply s u a -> Reply s u a-mergeErrorReply err1 reply -- XXX where to put it?-    =+mergeErrorReply err1 reply = -- XXX where to put it?     case reply of         Ok x state err2 -> Ok x state (mergeError err1 err2)         Error err2 -> Error (mergeError err1 err2)
data/examples/declaration/value/function/awkward-comment-0-out.hs view
@@ -1,6 +1,5 @@ mergeErrorReply :: ParseError -> Reply s u a -> Reply s u a-mergeErrorReply err1 reply -- XXX where to put it?-  =+mergeErrorReply err1 reply = -- XXX where to put it?   case reply of     Ok x state err2 -> Ok x state (mergeError err1 err2)     Error err2 -> Error (mergeError err1 err2)
data/examples/declaration/value/function/awkward-comment-1-four-out.hs view
@@ -1,8 +1,7 @@ doForeign :: Vars -> [Name] -> [Term] -> Idris LExp doForeign x = x   where-    splitArg tm | (_, [_, _, l, r]) <- unApply tm -- pair, two implicits-        =+    splitArg tm | (_, [_, _, l, r]) <- unApply tm = -- pair, two implicits         do             let l' = toFDesc l             r' <- irTerm (sMN 0 "__foreignCall") vs env r
data/examples/declaration/value/function/awkward-comment-1-out.hs view
@@ -1,8 +1,7 @@ doForeign :: Vars -> [Name] -> [Term] -> Idris LExp doForeign x = x   where-    splitArg tm | (_, [_, _, l, r]) <- unApply tm -- pair, two implicits-      =+    splitArg tm | (_, [_, _, l, r]) <- unApply tm = -- pair, two implicits       do         let l' = toFDesc l         r' <- irTerm (sMN 0 "__foreignCall") vs env r
+ data/examples/declaration/value/function/case-comment-after-pattern-four-out.hs view
@@ -0,0 +1,3 @@+foo = case a of+    b -> -- comment+        c
+ data/examples/declaration/value/function/case-comment-after-pattern-out.hs view
@@ -0,0 +1,3 @@+foo = case a of+  b -> -- comment+    c
+ data/examples/declaration/value/function/case-comment-after-pattern.hs view
@@ -0,0 +1,3 @@+foo = case a of+  b -- comment+    -> c
+ data/examples/declaration/value/function/case-comment-between-alt-and-where-four-out.hs view
@@ -0,0 +1,6 @@+foo =+    case x of+        _ -> 1+  -- comment+  where+    x = 1
+ data/examples/declaration/value/function/case-comment-between-alt-and-where-out.hs view
@@ -0,0 +1,6 @@+foo =+  case x of+    _ -> 1+  -- comment+  where+    x = 1
+ data/examples/declaration/value/function/case-comment-between-alt-and-where.hs view
@@ -0,0 +1,6 @@+foo =+  case x of+    _ -> 1+    -- comment+    where+      x = 1
data/examples/declaration/value/function/case-multi-line-four-out.hs view
@@ -21,7 +21,6 @@ quux x = case x of     x -> x -funnyComment =-    -- comment+funnyComment = -- comment     case () of         () -> ()
data/examples/declaration/value/function/case-multi-line-out.hs view
@@ -21,7 +21,6 @@ quux x = case x of   x -> x -funnyComment =-  -- comment+funnyComment = -- comment   case () of     () -> ()
+ data/examples/declaration/value/function/case-single-line-with-braces-four-out.hs view
@@ -0,0 +1,2 @@+getValue :: Maybe Int -> Int+getValue x = case x of Just n -> n; Nothing -> 0
+ data/examples/declaration/value/function/case-single-line-with-braces-out.hs view
@@ -0,0 +1,2 @@+getValue :: Maybe Int -> Int+getValue x = case x of Just n -> n; Nothing -> 0
+ data/examples/declaration/value/function/case-single-line-with-braces.hs view
@@ -0,0 +1,2 @@+getValue :: Maybe Int -> Int+getValue x = case x of {Just n -> n; Nothing -> 0}
+ data/examples/declaration/value/function/case-with-comment-before-where-four-out.hs view
@@ -0,0 +1,14 @@+foo =+    case x of+        _ -> 1+  -- comment+  where+    -- comment 2+    x = 1++foo = case x of+    _ -> 1+  -- comment+  where+    -- comment 2+    x = 1
+ data/examples/declaration/value/function/case-with-comment-before-where-out.hs view
@@ -0,0 +1,14 @@+foo =+  case x of+    _ -> 1+  -- comment+  where+    -- comment 2+    x = 1++foo = case x of+  _ -> 1+  -- comment+  where+    -- comment 2+    x = 1
+ data/examples/declaration/value/function/case-with-comment-before-where.hs view
@@ -0,0 +1,14 @@+foo =+  case x of+    _ -> 1+    -- comment+    where+      -- comment 2+      x = 1++foo = case x of+    _ -> 1+    -- comment+    where+      -- comment 2+      x = 1
+ data/examples/declaration/value/function/do-multiline-with-case-four-out.hs view
@@ -0,0 +1,13 @@+handleInput :: IO ()+handleInput = do+    putStrLn "Enter command:"+    cmd <- getLine+    case cmd of+        "quit" -> putStrLn "Goodbye"+        "help" -> do+            putStrLn "Available commands:"+            putStrLn "  quit - exit the program"+            putStrLn "  help - show this message"+        _ -> do+            putStrLn $ "Unknown command: " ++ cmd+            handleInput
+ data/examples/declaration/value/function/do-multiline-with-case-out.hs view
@@ -0,0 +1,13 @@+handleInput :: IO ()+handleInput = do+  putStrLn "Enter command:"+  cmd <- getLine+  case cmd of+    "quit" -> putStrLn "Goodbye"+    "help" -> do+      putStrLn "Available commands:"+      putStrLn "  quit - exit the program"+      putStrLn "  help - show this message"+    _ -> do+      putStrLn $ "Unknown command: " ++ cmd+      handleInput
+ data/examples/declaration/value/function/do-multiline-with-case.hs view
@@ -0,0 +1,13 @@+handleInput :: IO ()+handleInput = do+  putStrLn "Enter command:"+  cmd <- getLine+  case cmd of+    "quit" -> putStrLn "Goodbye"+    "help" -> do+      putStrLn "Available commands:"+      putStrLn "  quit - exit the program"+      putStrLn "  help - show this message"+    _ -> do+      putStrLn $ "Unknown command: " ++ cmd+      handleInput
+ data/examples/declaration/value/function/do-single-line-case-guards-four-out.hs view
@@ -0,0 +1,2 @@+checkValue :: Int -> IO ()+checkValue n = do putStr "Value is: "; case () of { _ | n < 0 -> putStrLn "negative" | n == 0 -> putStrLn "zero" | otherwise -> putStrLn "positive" }
+ data/examples/declaration/value/function/do-single-line-case-guards-out.hs view
@@ -0,0 +1,2 @@+checkValue :: Int -> IO ()+checkValue n = do putStr "Value is: "; case () of { _ | n < 0 -> putStrLn "negative" | n == 0 -> putStrLn "zero" | otherwise -> putStrLn "positive" }
+ data/examples/declaration/value/function/do-single-line-case-guards.hs view
@@ -0,0 +1,2 @@+checkValue :: Int -> IO ()+checkValue n = do {putStr "Value is: "; case () of {_ | n < 0 -> putStrLn "negative" | n == 0 -> putStrLn "zero" | otherwise -> putStrLn "positive"}}
+ data/examples/declaration/value/function/do-single-line-lambda-case-four-out.hs view
@@ -0,0 +1,2 @@+processValue :: Maybe Int -> IO ()+processValue x = do putStrLn "Processing:"; \case { Just n -> print n; Nothing -> putStrLn "Empty" } x; putStrLn "Done"
+ data/examples/declaration/value/function/do-single-line-lambda-case-out.hs view
@@ -0,0 +1,2 @@+processValue :: Maybe Int -> IO ()+processValue x = do putStrLn "Processing:"; \case { Just n -> print n; Nothing -> putStrLn "Empty" } x; putStrLn "Done"
+ data/examples/declaration/value/function/do-single-line-lambda-case.hs view
@@ -0,0 +1,2 @@+processValue :: Maybe Int -> IO ()+processValue x = do {putStrLn "Processing:"; \case {Just n -> print n; Nothing -> putStrLn "Empty"} x; putStrLn "Done"}
+ data/examples/declaration/value/function/do-single-line-multiple-cases-four-out.hs view
@@ -0,0 +1,2 @@+processPair :: Maybe Int -> Maybe String -> IO ()+processPair x y = do case x of { Just n -> print n; Nothing -> putStrLn "No number" }; case y of { Just s -> putStrLn s; Nothing -> putStrLn "No string" }
+ data/examples/declaration/value/function/do-single-line-multiple-cases-out.hs view
@@ -0,0 +1,2 @@+processPair :: Maybe Int -> Maybe String -> IO ()+processPair x y = do case x of { Just n -> print n; Nothing -> putStrLn "No number" }; case y of { Just s -> putStrLn s; Nothing -> putStrLn "No string" }
+ data/examples/declaration/value/function/do-single-line-multiple-cases.hs view
@@ -0,0 +1,2 @@+processPair :: Maybe Int -> Maybe String -> IO ()+processPair x y = do {case x of {Just n -> print n; Nothing -> putStrLn "No number"}; case y of {Just s -> putStrLn s; Nothing -> putStrLn "No string"}}
+ data/examples/declaration/value/function/do-single-line-nested-case-four-out.hs view
@@ -0,0 +1,2 @@+nestedDo :: Either String Int -> IO ()+nestedDo e = do putStr "Start: "; case e of { Left s -> do { putStr "Error: "; putStrLn s }; Right n -> do { putStr "Value: "; print n } }; putStrLn "End"
+ data/examples/declaration/value/function/do-single-line-nested-case-out.hs view
@@ -0,0 +1,2 @@+nestedDo :: Either String Int -> IO ()+nestedDo e = do putStr "Start: "; case e of { Left s -> do { putStr "Error: "; putStrLn s }; Right n -> do { putStr "Value: "; print n } }; putStrLn "End"
+ data/examples/declaration/value/function/do-single-line-nested-case.hs view
@@ -0,0 +1,2 @@+nestedDo :: Either String Int -> IO ()+nestedDo e = do {putStr "Start: "; case e of {Left s -> do {putStr "Error: "; putStrLn s}; Right n -> do {putStr "Value: "; print n}}; putStrLn "End"}
+ data/examples/declaration/value/function/do-single-line-with-case-four-out.hs view
@@ -0,0 +1,2 @@+doGuessing :: (Ord t, Read t) => t -> IO ()+doGuessing num = do putStrLn "Enter your guess:"; guess <- getLine; case compare (read guess) num of { LT -> do { putStrLn "Too low!"; doGuessing num }; GT -> do { putStrLn "Too high!"; doGuessing num }; EQ -> putStrLn "You win!" }
+ data/examples/declaration/value/function/do-single-line-with-case-out.hs view
@@ -0,0 +1,2 @@+doGuessing :: (Ord t, Read t) => t -> IO ()+doGuessing num = do putStrLn "Enter your guess:"; guess <- getLine; case compare (read guess) num of { LT -> do { putStrLn "Too low!"; doGuessing num }; GT -> do { putStrLn "Too high!"; doGuessing num }; EQ -> putStrLn "You win!" }
+ data/examples/declaration/value/function/do-single-line-with-case.hs view
@@ -0,0 +1,2 @@+doGuessing :: (Ord t, Read t) => t -> IO ()+doGuessing num = do {putStrLn "Enter your guess:"; guess <- getLine; case compare (read guess) num of {LT -> do {putStrLn "Too low!"; doGuessing num}; GT -> do { putStrLn "Too high!"; doGuessing num}; EQ -> putStrLn "You win!"}}
data/examples/declaration/value/function/guards-four-out.hs view
@@ -10,3 +10,5 @@ quux :: Int -> Int quux x | x < 0 = x quux x = x++(a, b) | c = d
data/examples/declaration/value/function/guards-out.hs view
@@ -10,3 +10,5 @@ quux :: Int -> Int quux x | x < 0 = x quux x = x++(a, b) | c = d
data/examples/declaration/value/function/guards.hs view
@@ -10,3 +10,5 @@ quux :: Int -> Int quux x | x < 0 = x quux x = x++(a, b) | c = d
data/examples/declaration/value/function/infix/dollar-chains-1-four-out.hs view
@@ -13,9 +13,9 @@         throwIO (OrmoluCppEnabled path)  foo =-    bar $-        baz $-            quux+    bar+        $ baz+        $ quux  x =     case l of { A -> B } $
data/examples/declaration/value/function/infix/dollar-chains-1-out.hs view
@@ -13,9 +13,9 @@     throwIO (OrmoluCppEnabled path)  foo =-  bar $-    baz $-      quux+  bar+    $ baz+    $ quux  x =   case l of { A -> B } $
data/examples/declaration/value/function/infix/dollar-chains-3-four-out.hs view
@@ -1,11 +1,11 @@ ex1 =-    f1 $-        arg1 $-            arg2 $-                arg3+    f1+        $ arg1+        $ arg2+        $ arg3  ex3 =-    f1 $-        arg1 $-            arg2 $-                1 + 3+    f1+        $ arg1+        $ arg2+        $ 1 + 3
data/examples/declaration/value/function/infix/dollar-chains-3-out.hs view
@@ -1,11 +1,11 @@ ex1 =-  f1 $-    arg1 $-      arg2 $-        arg3+  f1+    $ arg1+    $ arg2+    $ arg3  ex3 =-  f1 $-    arg1 $-      arg2 $-        1 + 3+  f1+    $ arg1+    $ arg2+    $ 1 + 3
data/examples/declaration/value/function/infix/dollar-chains-4-four-out.hs view
@@ -1,5 +1,5 @@ ex2 =-    f1 $-        arg1 $-            arg2 $-                f2 arg3+    f1+        $ arg1+        $ arg2+        $ f2 arg3
data/examples/declaration/value/function/infix/dollar-chains-4-out.hs view
@@ -1,5 +1,5 @@ ex2 =-  f1 $-    arg1 $-      arg2 $-        f2 arg3+  f1+    $ arg1+    $ arg2+    $ f2 arg3
data/examples/declaration/value/function/infix/lenses-four-out.hs view
@@ -1,14 +1,14 @@ import Control.Lens  lenses =-    Just $-        M.fromList $-            "type" .= ("user.connection" :: Text)-                # "connection" .= uc-                # "user" .= case name of-                    Just n -> Just $ object ["name" .= n]-                    Nothing -> Nothing-                # []+    Just+        $ M.fromList+        $ "type" .= ("user.connection" :: Text)+            # "connection" .= uc+            # "user" .= case name of+                Just n -> Just $ object ["name" .= n]+                Nothing -> Nothing+            # []  foo =     a
data/examples/declaration/value/function/infix/lenses-out.hs view
@@ -1,14 +1,14 @@ import Control.Lens  lenses =-  Just $-    M.fromList $-      "type" .= ("user.connection" :: Text)-        # "connection" .= uc-        # "user" .= case name of-          Just n -> Just $ object ["name" .= n]-          Nothing -> Nothing-        # []+  Just+    $ M.fromList+    $ "type" .= ("user.connection" :: Text)+      # "connection" .= uc+      # "user" .= case name of+        Just n -> Just $ object ["name" .= n]+        Nothing -> Nothing+      # []  foo =   a
data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1-four-out.hs view
@@ -1,11 +1,11 @@ -- Right chain, $ case, 2 operators with p($) == p(b) n :: Int n =-    1 $-        2 $-            3 $-                4 `seq`-                    5 $-                        6 $-                            7 $-                                8+    1+        $ 2+        $ 3+        $ 4+        `seq` 5+        $ 6+        $ 7+        $ 8
data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1-out.hs view
@@ -1,11 +1,11 @@ -- Right chain, $ case, 2 operators with p($) == p(b) n :: Int n =-  1 $-    2 $-      3 $-        4 `seq`-          5 $-            6 $-              7 $-                8+  1+    $ 2+    $ 3+    $ 4+    `seq` 5+    $ 6+    $ 7+    $ 8
data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2-four-out.hs view
@@ -1,11 +1,11 @@ -- Right chain, $ case, 2 operators with p(a) == p($) p :: Int p =-    1 `seq`-        2 `seq`-            3 `seq`-                4 $-                    5 `seq`-                        6 `seq`-                            7 `seq`-                                8+    1+        `seq` 2+        `seq` 3+        `seq` 4+        $ 5+        `seq` 6+        `seq` 7+        `seq` 8
data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2-out.hs view
@@ -1,11 +1,11 @@ -- Right chain, $ case, 2 operators with p(a) == p($) p :: Int p =-  1 `seq`-    2 `seq`-      3 `seq`-        4 $-          5 `seq`-            6 `seq`-              7 `seq`-                8+  1+    `seq` 2+    `seq` 3+    `seq` 4+    $ 5+    `seq` 6+    `seq` 7+    `seq` 8
data/examples/declaration/value/function/infix/op-chain-r-s-dollar-four-out.hs view
@@ -1,7 +1,7 @@ -- Right chain, $ case, 1 operator type c :: Int c =-    1 $-        2 $-            3 $-                4+    1+        $ 2+        $ 3+        $ 4
data/examples/declaration/value/function/infix/op-chain-r-s-dollar-out.hs view
@@ -1,7 +1,7 @@ -- Right chain, $ case, 1 operator type c :: Int c =-  1 $-    2 $-      3 $-        4+  1+    $ 2+    $ 3+    $ 4
data/examples/declaration/value/function/infix/qualified-ops-four-out.hs view
@@ -1,9 +1,9 @@ lenses =-    Just $-        M.fromList $-            "type" Foo..= ("user.connection" :: Text)-                Bar.# "connection" Foo..= uc-                Bar.# "user" Foo..= case name of-                    Just n -> Just $ object ["name" .= n]-                    Nothing -> Nothing-                Bar.# []+    Just+        $ M.fromList+        $ "type" Foo..= ("user.connection" :: Text)+            Bar.# "connection" Foo..= uc+            Bar.# "user" Foo..= case name of+                Just n -> Just $ object ["name" .= n]+                Nothing -> Nothing+            Bar.# []
data/examples/declaration/value/function/infix/qualified-ops-out.hs view
@@ -1,9 +1,9 @@ lenses =-  Just $-    M.fromList $-      "type" Foo..= ("user.connection" :: Text)-        Bar.# "connection" Foo..= uc-        Bar.# "user" Foo..= case name of-          Just n -> Just $ object ["name" .= n]-          Nothing -> Nothing-        Bar.# []+  Just+    $ M.fromList+    $ "type" Foo..= ("user.connection" :: Text)+      Bar.# "connection" Foo..= uc+      Bar.# "user" Foo..= case name of+        Just n -> Just $ object ["name" .= n]+        Nothing -> Nothing+      Bar.# []
+ data/examples/declaration/value/function/lambda-comment-after-arrow-four-out.hs view
@@ -0,0 +1,2 @@+f = \a -> -- foo+    a
+ data/examples/declaration/value/function/lambda-comment-after-arrow-out.hs view
@@ -0,0 +1,2 @@+f = \a -> -- foo+  a
+ data/examples/declaration/value/function/lambda-comment-after-arrow.hs view
@@ -0,0 +1,2 @@+f = \a -> -- foo+  a
+ data/examples/declaration/value/function/linear-bindings-four-out.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE LinearTypes #-}++h x = g y+  where+    %1 y = f x++let %1 x = u in ()+let %Many (x, y) = u in ()+let %1 ~(x, y) = u in ()
+ data/examples/declaration/value/function/linear-bindings-out.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE LinearTypes #-}++h x = g y+  where+    %1 y = f x++let %1 x = u in ()+let %Many (x, y) = u in ()+let %1 ~(x, y) = u in ()
+ data/examples/declaration/value/function/linear-bindings.hs view
@@ -0,0 +1,9 @@+{-# Language LinearTypes #-}++h x = g y+  where+    %1 y = f x++let %1 x = u in ()+let %Many (x, y) = u in ()+let %1 ~(x, y) = u in ()
+ data/examples/declaration/value/function/multiline-strings-9-four-out.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE MultilineStrings #-}++multilineBlank =+    """+    1+++++    6+    """
+ data/examples/declaration/value/function/multiline-strings-9-out.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE MultilineStrings #-}++multilineBlank =+  """+  1+++++  6+  """
+ data/examples/declaration/value/function/multiline-strings-9.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE MultilineStrings #-}++multilineBlank =+  """+  1+++++  6+  """
data/examples/declaration/value/function/newline-single-line-body-four-out.hs view
@@ -4,7 +4,6 @@  function' :: String -> String function' s = case s of-    "ThisString" ->-        -- And a comment here is okay+    "ThisString" -> -- And a comment here is okay         "Yay"     _ -> "Boo"
data/examples/declaration/value/function/newline-single-line-body-out.hs view
@@ -4,7 +4,6 @@  function' :: String -> String function' s = case s of-  "ThisString" ->-    -- And a comment here is okay+  "ThisString" -> -- And a comment here is okay     "Yay"   _ -> "Boo"
+ data/examples/declaration/value/function/operator-comments-3-four-out.hs view
@@ -0,0 +1,5 @@+data X = X {x :: Int}++f =+    id+        . (\s -> s{x = 1}) -- Some comment
+ data/examples/declaration/value/function/operator-comments-3-out.hs view
@@ -0,0 +1,5 @@+data X = X {x :: Int}++f =+  id+    . (\s -> s {x = 1}) -- Some comment
+ data/examples/declaration/value/function/operator-comments-3.hs view
@@ -0,0 +1,5 @@+data X = X { x :: Int }++f = id+    . -- Some comment+    (\s -> s { x = 1 })
+ data/examples/declaration/value/function/operator-comments-4-four-out.hs view
@@ -0,0 +1,4 @@+foo = do+    bar+        -- txt+        $ baz
+ data/examples/declaration/value/function/operator-comments-4-out.hs view
@@ -0,0 +1,4 @@+foo = do+  bar+    -- txt+    $ baz
+ data/examples/declaration/value/function/operator-comments-4.hs view
@@ -0,0 +1,4 @@+foo = do+  bar+    -- txt+    $ baz
data/examples/declaration/value/function/pattern/or-patterns-four-out.hs view
@@ -20,7 +20,8 @@ sane e = case e of     1     2-    3 -> a+    3 ->+        a     4     5     6 -> b@@ -33,3 +34,10 @@     (D; E (Just _) Nothing) ->         4     F -> 5++food+    foo@( A;+            B;+            C+            ) = Just foo+food _ = Nothing
data/examples/declaration/value/function/pattern/or-patterns-out.hs view
@@ -21,7 +21,8 @@ sane e = case e of   1   2-  3 -> a+  3 ->+    a   4   5   6 -> b@@ -34,3 +35,10 @@   (D; E (Just _) Nothing) ->     4   F -> 5++food+  foo@( A;+        B;+        C+        ) = Just foo+food _ = Nothing
data/examples/declaration/value/function/pattern/or-patterns.hs view
@@ -20,7 +20,8 @@ sane e = case e of   1   2-  3 -> a+  3 ->+    a   4   5;6 -> b   7;8 -> c@@ -31,3 +32,8 @@   (D; E (Just _) Nothing)    -> 4   F -> 5++food foo@(A;+          B;+          C) = Just foo+food _ = Nothing
+ data/examples/declaration/value/function/record/wildcard-comments-0-four-out.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE RecordWildCards #-}++example =+    Record+        { -- A+          field = () -- B+          -- C+        , field = () -- D+        -- E+        -- F+        , ..+        } -- G
+ data/examples/declaration/value/function/record/wildcard-comments-0-out.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE RecordWildCards #-}++example =+  Record+    { -- A+      field = (), -- B+      -- C+      field = (), -- D+      -- E+      -- F+      ..+    } -- G
+ data/examples/declaration/value/function/record/wildcard-comments-0.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE RecordWildCards #-}++example =+  Record+    { -- A+      field = (), -- B+      -- C+      field = (), -- D+      -- E+      .. -- F+    } -- G
+ data/examples/declaration/value/function/record/wildcard-comments-1-four-out.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE RecordWildCards #-}++example =+    Record+        { -- A+          field = ()+        , -- C+          field = ()+          -- E+          -- F+        , ..+        } -- G
+ data/examples/declaration/value/function/record/wildcard-comments-1-out.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE RecordWildCards #-}++example =+  Record+    { -- A+      field = (),+      -- C+      field = (),+      -- E+      -- F+      ..+    } -- G
+ data/examples/declaration/value/function/record/wildcard-comments-1.hs view
@@ -0,0 +1,11 @@+{-# LANGUAGE RecordWildCards #-}++example =+  Record+    { -- A+      field = (),+      -- C+      field = (),+      -- E+      .. -- F+    } -- G
data/examples/declaration/value/function/strings-four-out.hs view
@@ -8,3 +8,5 @@     \baz"  weirdGap = "\65\ \0"++weirdEscape = "\^\ "
data/examples/declaration/value/function/strings-out.hs view
@@ -10,3 +10,5 @@   \baz"  weirdGap = "\65\ \0"++weirdEscape = "\^\ "
data/examples/declaration/value/function/strings.hs view
@@ -7,3 +7,5 @@     \baz"  weirdGap = "\65\ \0"++weirdEscape = "\^\ "
data/examples/declaration/value/pattern-synonyms/explicitely-bidirectional-four-out.hs view
@@ -3,19 +3,19 @@ pattern P a <- C a where P a = C a  pattern HeadC x <- x : xs-    where-        HeadC x = [x]+  where+    HeadC x = [x]  pattern HeadC' x <-     x : xs-    where-        HeadC' x = [x]+  where+    HeadC' x = [x]  pattern Simple <- "Simple"-    where-        Simple = "Complicated"+  where+    Simple = "Complicated"  pattern a :< b <-     (a, b)-    where-        a :< b = (a, b)+  where+    a :< b = (a, b)
+ data/examples/fixity/megaparsec-alternative-four-out.hs view
@@ -0,0 +1,8 @@+module MegaparsecExample where++import Text.Megaparsec++pValue =+    Object <$> parseObjectBody+        <|> Array <$> parseArrayBody+        <|> String <$> parseStringBody
+ data/examples/fixity/megaparsec-alternative-out.hs view
@@ -0,0 +1,8 @@+module MegaparsecExample where++import Text.Megaparsec++pValue =+  Object <$> parseObjectBody+    <|> Array <$> parseArrayBody+    <|> String <$> parseStringBody
+ data/examples/fixity/megaparsec-alternative.hs view
@@ -0,0 +1,8 @@+module MegaparsecExample where++import Text.Megaparsec++pValue =+  Object <$> parseObjectBody+    <|> Array <$> parseArrayBody+    <|> String <$> parseStringBody
+ data/examples/fixity/optics-mixed-four-out.hs view
@@ -0,0 +1,8 @@+module OpticsExample where++import Optics++updated =+    record+        & fieldLens % subFieldLens .~ someValue+        & otherLens %~ someTransformationFunctionApplied
+ data/examples/fixity/optics-mixed-out.hs view
@@ -0,0 +1,8 @@+module OpticsExample where++import Optics++updated =+  record+    & fieldLens % subFieldLens .~ someValue+    & otherLens %~ someTransformationFunctionApplied
+ data/examples/fixity/optics-mixed.hs view
@@ -0,0 +1,8 @@+module OpticsExample where++import Optics++updated =+  record+    & fieldLens % subFieldLens .~ someValue+    & otherLens %~ someTransformationFunctionApplied
+ data/examples/fixity/relude-default-operator-chain-four-out.hs view
@@ -0,0 +1,10 @@+module ReludeChainExample where++import Relude++resolveValue =+    primarySource+        ?: secondarySource+        ?: tertiarySource+        ?: quaternarySource+        ?: finalFallbackValue
+ data/examples/fixity/relude-default-operator-chain-out.hs view
@@ -0,0 +1,10 @@+module ReludeChainExample where++import Relude++resolveValue =+  primarySource+    ?: secondarySource+    ?: tertiarySource+    ?: quaternarySource+    ?: finalFallbackValue
+ data/examples/fixity/relude-default-operator-chain.hs view
@@ -0,0 +1,10 @@+module ReludeChainExample where++import Relude++resolveValue =+  primarySource+    ?: secondarySource+    ?: tertiarySource+    ?: quaternarySource+    ?: finalFallbackValue
+ data/examples/fixity/relude-default-operator-four-out.hs view
@@ -0,0 +1,7 @@+module ReludeExample where++import Relude++config =+    lookupOptionalSetting environment ?:+        defaultConfigurationValue
+ data/examples/fixity/relude-default-operator-out.hs view
@@ -0,0 +1,7 @@+module ReludeExample where++import Relude++config =+  lookupOptionalSetting environment ?:+    defaultConfigurationValue
+ data/examples/fixity/relude-default-operator.hs view
@@ -0,0 +1,7 @@+module ReludeExample where++import Relude++config =+  lookupOptionalSetting environment+    ?: defaultConfigurationValue
+ data/examples/fixity/rio-ampersand-four-out.hs view
@@ -0,0 +1,7 @@+module RioExample where++import RIO++message =+    greetingText <> userNameText+        & Text.strip
+ data/examples/fixity/rio-ampersand-out.hs view
@@ -0,0 +1,7 @@+module RioExample where++import RIO++message =+  greetingText <> userNameText+    & Text.strip
+ data/examples/fixity/rio-ampersand.hs view
@@ -0,0 +1,7 @@+module RioExample where++import RIO++message =+  greetingText <> userNameText+    & Text.strip
+ data/examples/fixity/rio-deepseq-four-out.hs view
@@ -0,0 +1,7 @@+module RioDeepseqExample where++import RIO++result =+    forceEvaluationOfBigStructure `deepseq`+        continueWithNextStep
+ data/examples/fixity/rio-deepseq-out.hs view
@@ -0,0 +1,7 @@+module RioDeepseqExample where++import RIO++result =+  forceEvaluationOfBigStructure `deepseq`+    continueWithNextStep
+ data/examples/fixity/rio-deepseq.hs view
@@ -0,0 +1,7 @@+module RioDeepseqExample where++import RIO++result =+  forceEvaluationOfBigStructure+    `deepseq` continueWithNextStep
+ data/examples/import/comment-before-merged-import-lists-four-out.hs view
@@ -0,0 +1,7 @@+-- their own formatters.+import Test.Hspec.Core.Formatters.V1.Monad (+    FormatM,+    Formatter (..),+ )++import Test.Hspec.Core.Formatters.V1.Monad (Item (..), interpretWith)
+ data/examples/import/comment-before-merged-import-lists-out.hs view
@@ -0,0 +1,7 @@+-- their own formatters.+import Test.Hspec.Core.Formatters.V1.Monad+  ( FormatM,+    Formatter (..),+    Item (..),+    interpretWith,+  )
+ data/examples/import/comment-before-merged-import-lists.hs view
@@ -0,0 +1,7 @@+-- their own formatters.+import Test.Hspec.Core.Formatters.V1.Monad (+    Formatter(..)+  , FormatM+  )++import Test.Hspec.Core.Formatters.V1.Monad (Item(..), interpretWith)
+ data/examples/import/comment-before-merged-imports-four-out.hs view
@@ -0,0 +1,7 @@+-- Import stuff from Prelude explicitly+import Prelude (+    Eq (..),+    Int,+    ($),+    (.),+ )
+ data/examples/import/comment-before-merged-imports-out.hs view
@@ -0,0 +1,7 @@+-- Import stuff from Prelude explicitly+import Prelude+  ( Eq (..),+    Int,+    ($),+    (.),+  )
+ data/examples/import/comment-before-merged-imports.hs view
@@ -0,0 +1,3 @@+-- Import stuff from Prelude explicitly+import Prelude (Eq(..), Int)+import Prelude ((.), ($))
+ data/examples/import/comment-between-merged-imports-four-out.hs view
@@ -0,0 +1,6 @@+import HscMain (newHscEnv)+-- Implementations of the various modes+import LoadIface (showIface)++-- Imports for --abi-hash+import LoadIface (loadUserInterface)
+ data/examples/import/comment-between-merged-imports-out.hs view
@@ -0,0 +1,7 @@+import HscMain (newHscEnv)+-- Implementations of the various modes+import LoadIface+  ( -- Imports for --abi-hash+    loadUserInterface,+    showIface,+  )
+ data/examples/import/comment-between-merged-imports.hs view
@@ -0,0 +1,6 @@+-- Implementations of the various modes+import           LoadIface ( showIface )+import           HscMain ( newHscEnv )++-- Imports for --abi-hash+import           LoadIface ( loadUserInterface )
+ data/examples/import/comment-inside-empty-import-list-four-out.hs view
@@ -0,0 +1,6 @@+import Package1+import Package2+import Package3 (++    -- , import1+ )
+ data/examples/import/comment-inside-empty-import-list-out.hs view
@@ -0,0 +1,6 @@+import Package1+import Package2+import Package3+  (+  -- , import1+  )
+ data/examples/import/comment-inside-empty-import-list.hs view
@@ -0,0 +1,5 @@+import Package1+import Package3 (+ -- , import1+ )+import Package2
+ data/examples/import/comment-inside-sorted-import-list-four-out.hs view
@@ -0,0 +1,7 @@+import Package1+import Package2+import Package3 (+    hi,+    -- , import1+    test,+ )
+ data/examples/import/comment-inside-sorted-import-list-out.hs view
@@ -0,0 +1,7 @@+import Package1+import Package2+import Package3+  ( hi,+    -- , import1+    test,+  )
+ data/examples/import/comment-inside-sorted-import-list.hs view
@@ -0,0 +1,7 @@+import Package1+import Package3 (+ hi,+ -- , import1+ test,+ )+import Package2
data/examples/import/comments-inside-imports-out.hs view
@@ -1,7 +1,6 @@--- x- import qualified -- x   Bar import qualified -- x   Baz-import Foo+import -- x+  Foo
data/examples/import/comments-per-import-four-out.hs view
@@ -1,4 +1,3 @@--- (1) import Bar -- (2) import Baz -- (3)-import Foo+import Foo -- (1)
data/examples/import/comments-per-import-out.hs view
@@ -1,4 +1,3 @@--- (1) import Bar -- (2) import Baz -- (3)-import Foo+import Foo -- (1)
+ data/examples/import/data-four-out.hs view
@@ -0,0 +1,7 @@+module Bar (data P, T (data P), data f) where++import N (+    T (data P),+    data P,+    data f,+ )
+ data/examples/import/data-out.hs view
@@ -0,0 +1,7 @@+module Bar (data P, T (data P), data f) where++import N+  ( T (data P),+    data P,+    data f,+  )
+ data/examples/import/data.hs view
@@ -0,0 +1,6 @@++module Bar (data P, T(data P), data f) where++import N (data P)+import N (T(data P))+import N (data f)
data/examples/import/explicit-imports-with-comments-four-out.hs view
@@ -1,7 +1,5 @@ import qualified MegaModule as M (-    -- (1)-    -- (2)     Either, -- (3)-    (<<<),-    (>>>),+    (<<<), -- (2)+    (>>>), -- (1)  )
data/examples/import/explicit-imports-with-comments-out.hs view
@@ -1,7 +1,5 @@ import qualified MegaModule as M-  ( -- (1)-    -- (2)-    Either, -- (3)-    (<<<),-    (>>>),+  ( Either, -- (3)+    (<<<), -- (2)+    (>>>), -- (1)   )
+ data/examples/import/explicit-level-imports-four-out.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE ExplicitLevelImports #-}++import A splice+import {-# SOURCE #-} safe qualified A splice as QA hiding (a, b, c, d, e, f)+import quote qualified B as QB+import qualified C splice as SC+import qualified D splice+import Data.ByteString (e)+import Data.ByteString.Lazy quote (d)+import splice Data.Text (a, b, c)+import PyF ()+import splice PyF (+    fmt,+    tmf,+ )+import quote PyF (abc)
+ data/examples/import/explicit-level-imports-out.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE ExplicitLevelImports #-}++import A splice+import {-# SOURCE #-} safe qualified A splice as QA hiding (a, b, c, d, e, f)+import quote qualified B as QB+import qualified C splice as SC+import qualified D splice+import Data.ByteString (e)+import Data.ByteString.Lazy quote (d)+import splice Data.Text (a, b, c)+import PyF ()+import splice PyF+  ( fmt,+    tmf,+  )+import quote PyF (abc)
+ data/examples/import/explicit-level-imports-qualified-post-four-out.hs view
@@ -0,0 +1,5 @@+{-# LANGUAGE ExplicitLevelImports #-}+{-# LANGUAGE ImportQualifiedPost #-}++import quote A qualified as QA+import B quote qualified as QB
+ data/examples/import/explicit-level-imports-qualified-post-out.hs view
@@ -0,0 +1,5 @@+{-# LANGUAGE ExplicitLevelImports #-}+{-# LANGUAGE ImportQualifiedPost #-}++import quote A qualified as QA+import B quote qualified as QB
+ data/examples/import/explicit-level-imports-qualified-post.hs view
@@ -0,0 +1,5 @@+{-# LANGUAGE ExplicitLevelImports #-}+{-# LANGUAGE ImportQualifiedPost #-}++import qualified B quote as QB+import quote qualified A as QA
+ data/examples/import/explicit-level-imports.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE ExplicitLevelImports #-}++import splice Data.Text (a, b, c)+import Data.ByteString.Lazy quote (d)+import Data.ByteString (e)+import {-# SOURCE #-} safe qualified A splice as QA hiding (a, b, c, d, e, f)+import quote qualified B as QB+import qualified C splice as SC+import A splice+import qualified D splice+import quote PyF (abc)+import splice PyF (fmt)+import splice PyF (tmf)+import PyF ()
+ data/examples/import/implicit-prelude-package-four-out.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE PackageImports #-}++import "base" Control.Applicative (Alternative, (<|>))+import "base" Data.Maybe (Maybe (Nothing), maybe)+import "base" System.IO (IO)+import "yaya" Yaya.Fold (ana, cata)+import "base" Prelude ((+))
+ data/examples/import/implicit-prelude-package-out.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE PackageImports #-}++import "base" Control.Applicative (Alternative, (<|>))+import "base" Data.Maybe (Maybe (Nothing), maybe)+import "base" System.IO (IO)+import "yaya" Yaya.Fold (ana, cata)+import "base" Prelude ((+))
+ data/examples/import/implicit-prelude-package.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE PackageImports #-}++import "base" System.IO (IO)+import "base" Prelude ((+))+import "yaya" Yaya.Fold (ana, cata)+import "base" Control.Applicative (Alternative, (<|>))+import "base" Data.Maybe (Maybe (Nothing), maybe)
data/examples/import/merging-0-four-out.hs view
@@ -1,3 +1,6 @@ import Foo-import Foo (bar, foo)+import Foo (+    bar,+    foo,+ ) import Foo as F
data/examples/import/merging-0-out.hs view
@@ -1,3 +1,6 @@ import Foo-import Foo (bar, foo)+import Foo+  ( bar,+    foo,+  ) import Foo as F
data/examples/import/merging-1-four-out.hs view
@@ -1,2 +1,5 @@ import "bar" Foo (bar)-import "foo" Foo (baz, foo)+import "foo" Foo (+    baz,+    foo,+ )
data/examples/import/merging-1-out.hs view
@@ -1,2 +1,5 @@ import "bar" Foo (bar)-import "foo" Foo (baz, foo)+import "foo" Foo+  ( baz,+    foo,+  )
data/examples/import/merging-2-four-out.hs view
@@ -1,2 +1,8 @@-import Foo hiding (bar4, foo2)-import qualified Foo (bar3, foo1)+import Foo hiding (+    bar4,+    foo2,+ )+import qualified Foo (+    bar3,+    foo1,+ )
data/examples/import/merging-2-out.hs view
@@ -1,2 +1,8 @@-import Foo hiding (bar4, foo2)-import qualified Foo (bar3, foo1)+import Foo hiding+  ( bar4,+    foo2,+  )+import qualified Foo+  ( bar3,+    foo1,+  )
+ data/examples/import/no-implicit-prelude-four-out.hs view
@@ -0,0 +1,6 @@+{-# LANGUAGE NoImplicitPrelude #-}++import Control.Applicative (Alternative, (<|>))+import Data.Maybe (Maybe (Nothing), maybe)+import Prelude ((+))+import System.IO (IO)
+ data/examples/import/no-implicit-prelude-out.hs view
@@ -0,0 +1,6 @@+{-# LANGUAGE NoImplicitPrelude #-}++import Control.Applicative (Alternative, (<|>))+import Data.Maybe (Maybe (Nothing), maybe)+import Prelude ((+))+import System.IO (IO)
+ data/examples/import/no-implicit-prelude-package-four-out.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE NoImplicitPrelude #-}++import "base" Control.Applicative (Alternative, (<|>))+import "base" Data.Maybe (Maybe (Nothing), maybe)+import "base" Prelude ((+))+import "base" System.IO (IO)+import "yaya" Yaya.Fold (ana, cata)
+ data/examples/import/no-implicit-prelude-package-out.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE NoImplicitPrelude #-}++import "base" Control.Applicative (Alternative, (<|>))+import "base" Data.Maybe (Maybe (Nothing), maybe)+import "base" Prelude ((+))+import "base" System.IO (IO)+import "yaya" Yaya.Fold (ana, cata)
+ data/examples/import/no-implicit-prelude-package.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE PackageImports #-}++import "base" System.IO (IO)+import "base" Prelude ((+))+import "yaya" Yaya.Fold (ana, cata)+import "base" Control.Applicative (Alternative, (<|>))+import "base" Data.Maybe (Maybe (Nothing), maybe)
+ data/examples/import/no-implicit-prelude.hs view
@@ -0,0 +1,6 @@+{-# LANGUAGE NoImplicitPrelude #-}++import System.IO (IO)+import Prelude ((+))+import Control.Applicative (Alternative, (<|>))+import Data.Maybe (Maybe (Nothing), maybe)
data/examples/import/simple-four-out.hs view
@@ -1,5 +1,13 @@ import Data.Text-import Data.Text (a, b, c)-import Data.Text hiding (a, b, c)+import Data.Text (+    a,+    b,+    c,+ )+import Data.Text hiding (+    a,+    b,+    c,+ ) import qualified Data.Text (a, b, c) import qualified Data.Text as T
data/examples/import/simple-out.hs view
@@ -1,5 +1,13 @@ import Data.Text-import Data.Text (a, b, c)-import Data.Text hiding (a, b, c)+import Data.Text+  ( a,+    b,+    c,+  )+import Data.Text hiding+  ( a,+    b,+    c,+  ) import qualified Data.Text (a, b, c) import qualified Data.Text as T
+ data/examples/module-header/block-haddock-in-export-list-four-out.hs view
@@ -0,0 +1,4 @@+module Foo (+    -- | asdf+    foo,+) where
+ data/examples/module-header/block-haddock-in-export-list-out.hs view
@@ -0,0 +1,5 @@+module Foo+  ( {- | asdf -}+    foo,+  )+where
+ data/examples/module-header/block-haddock-in-export-list.hs view
@@ -0,0 +1,1 @@+module Foo ({- | asdf -} foo) where
data/examples/module-header/empty-haddock-four-out.hs view
@@ -1,1 +1,3 @@+-- \|+-- module Test where
data/examples/module-header/empty-haddock-out.hs view
@@ -1,1 +1,3 @@+-- \|+-- module Test where
+ data/examples/other/block-comment-before-argument-four-out.hs view
@@ -0,0 +1,5 @@+checkPragma =+    ifM+        (anyM isBuiltin [builtinNat, builtinBool])+        {-then-} ok+        {-else-} notPostulate
+ data/examples/other/block-comment-before-argument-out.hs view
@@ -0,0 +1,5 @@+checkPragma =+  ifM+    (anyM isBuiltin [builtinNat, builtinBool])+    {-then-} ok+    {-else-} notPostulate
+ data/examples/other/block-comment-before-argument.hs view
@@ -0,0 +1,4 @@+checkPragma =+        ifM (anyM isBuiltin [builtinNat, builtinBool])+          {-then-} ok+          {-else-} notPostulate
+ data/examples/other/block-comment-before-element-four-out.hs view
@@ -0,0 +1,6 @@+eeExtensions =+    catMaybes+        [ {- 0x00 -} sniExt+        , {- 0x0a -} groupExt+        , {- 0x10 -} alpnExt+        ]
+ data/examples/other/block-comment-before-element-out.hs view
@@ -0,0 +1,6 @@+eeExtensions =+  catMaybes+    [ {- 0x00 -} sniExt,+      {- 0x0a -} groupExt,+      {- 0x10 -} alpnExt+    ]
+ data/examples/other/block-comment-before-element.hs view
@@ -0,0 +1,6 @@+eeExtensions =+    catMaybes+        [ {- 0x00 -} sniExt+        , {- 0x0a -} groupExt+        , {- 0x10 -} alpnExt+        ]
+ data/examples/other/comment-around-quasiquote-four-out.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE QuasiQuotes #-}++example =+    [ -- A+      [u||] -- B+      -- C+    , [u||] -- D+    -- E+    ] -- F
+ data/examples/other/comment-around-quasiquote-out.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE QuasiQuotes #-}++example =+  [ -- A+    [u||], -- B+    -- C+    [u||] -- D+    -- E+  ] -- F
+ data/examples/other/comment-around-quasiquote.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE QuasiQuotes #-}++example =+  [ -- A+    [u||], -- B+    -- C+    [u||] -- D+    -- E+  ] -- F
+ data/examples/other/comment-block-section-heading-four-out.hs view
@@ -0,0 +1,3 @@+{- ***+   aaa+-}
+ data/examples/other/comment-block-section-heading-out.hs view
@@ -0,0 +1,3 @@+{- ***+   aaa+-}
+ data/examples/other/comment-block-section-heading.hs view
@@ -0,0 +1,3 @@+{- ***+   aaa+-}
data/examples/other/comment-glued-together-out.hs view
@@ -1,6 +1,6 @@ module Main (main) where --- | Foo.+{- | Foo. -}  -- Bar main :: IO ()
+ data/examples/other/comment-in-empty-list-four-out.hs view
@@ -0,0 +1,9 @@+tests_Cli_Utils =+    testGroup+        "Utils"+        [++        --  testGroup "journalApplyValue" [+        --    testCase "time" $ do+        --  ]+        ]
+ data/examples/other/comment-in-empty-list-out.hs view
@@ -0,0 +1,9 @@+tests_Cli_Utils =+  testGroup+    "Utils"+    [++    --  testGroup "journalApplyValue" [+    --    testCase "time" $ do+    --  ]+    ]
+ data/examples/other/comment-in-empty-list.hs view
@@ -0,0 +1,6 @@+tests_Cli_Utils = testGroup "Utils" [++  --  testGroup "journalApplyValue" [+  --    testCase "time" $ do+  --  ]+  ]
+ data/examples/other/comment-opening-a-list-four-out.hs view
@@ -0,0 +1,11 @@+module Hledger.Cli.Commands where++commandsList :: String -> [String] -> [String]+commandsList progversion othercmds =+    map (bold' . accent) _banner_smslant+        ++ [ -- XXX not showing bold, why ?+             -- Keep the following synced with:+             --  commands.m4+             "----------"+           , progversion+           ]
+ data/examples/other/comment-opening-a-list-out.hs view
@@ -0,0 +1,11 @@+module Hledger.Cli.Commands where++commandsList :: String -> [String] -> [String]+commandsList progversion othercmds =+  map (bold' . accent) _banner_smslant+    ++ [ -- XXX not showing bold, why ?+         -- Keep the following synced with:+         --  commands.m4+         "----------",+         progversion+       ]
+ data/examples/other/comment-opening-a-list.hs view
@@ -0,0 +1,11 @@+module Hledger.Cli.Commands where++commandsList :: String -> [String] -> [String]+commandsList progversion othercmds =+  map (bold' . accent) _banner_smslant ++   -- XXX not showing bold, why ?+  [+  -- Keep the following synced with:+  --  commands.m4+   "----------"+  ,progversion+  ]
data/examples/other/comment-style-transform-out.hs view
@@ -1,17 +1,20 @@--- |--- Module:      Data.Aeson.TH--- Copyright:   (c) 2011-2016 Bryan O'Sullivan---              (c) 2011 MailRank, Inc.--- License:     BSD3--- Stability:   experimental--- Portability: portable+{-|+Module:      Data.Aeson.TH+Copyright:   (c) 2011-2016 Bryan O'Sullivan+             (c) 2011 MailRank, Inc.+License:     BSD3+Stability:   experimental+Portability: portable+-} module Main where --- |------ Here is a snippet:------ @--- x = y + 2--- @+{- |++Here is a snippet:++@+x = y + 2+@++-} x = y + 2
+ data/examples/other/comment-trailing-blank-line-do-four-out.hs view
@@ -0,0 +1,4 @@+doBlock = do+    a --++    b
+ data/examples/other/comment-trailing-blank-line-do-out.hs view
@@ -0,0 +1,4 @@+doBlock = do+  a --++  b
+ data/examples/other/comment-trailing-blank-line-do.hs view
@@ -0,0 +1,4 @@+doBlock = do+  a --++  b
+ data/examples/other/comment-trailing-blank-line-let-four-out.hs view
@@ -0,0 +1,5 @@+letBlock =+    let a = a --++        b = b+     in c
+ data/examples/other/comment-trailing-blank-line-let-out.hs view
@@ -0,0 +1,5 @@+letBlock =+  let a = a --++      b = b+   in c
+ data/examples/other/comment-trailing-blank-line-let.hs view
@@ -0,0 +1,5 @@+letBlock =+  let a = a --++      b = b+   in c
+ data/examples/other/comment-trailing-blank-line-variants-four-out.hs view
@@ -0,0 +1,22 @@+textComment = do+    a -- some text++    b++blockComment = do+    a {- foo -}++    b++twoComments = do+    a --++    --++    bar++adjacentThenBlank = do+    a --+    --++    bar
+ data/examples/other/comment-trailing-blank-line-variants-out.hs view
@@ -0,0 +1,22 @@+textComment = do+  a -- some text++  b++blockComment = do+  a {- foo -}++  b++twoComments = do+  a --++  --++  bar++adjacentThenBlank = do+  a --+  --++  bar
+ data/examples/other/comment-trailing-blank-line-variants.hs view
@@ -0,0 +1,22 @@+textComment = do+  a -- some text++  b++blockComment = do+  a {- foo -}++  b++twoComments = do+  a --++  --++  bar++adjacentThenBlank = do+  a --+  --++  bar
+ data/examples/other/comment-trailing-blank-line-where-four-out.hs view
@@ -0,0 +1,5 @@+whereBlock = foo+  where+    a = a --++    b = b
+ data/examples/other/comment-trailing-blank-line-where-out.hs view
@@ -0,0 +1,5 @@+whereBlock = foo+  where+    a = a --++    b = b
+ data/examples/other/comment-trailing-blank-line-where.hs view
@@ -0,0 +1,5 @@+whereBlock = foo+  where+    a = a --++    b = b
+ data/examples/other/comment-trailing-no-blank-line-four-out.hs view
@@ -0,0 +1,25 @@+noBlankAfterComment = do+    a --+    b++noBlankTextComment = do+    a -- some text+    b++noBlankBlockComment = do+    a {- foo -}+    b++blockCommentFollowedByExpr = do+    a {- foo -} 1+    b++whereNoBlank = foo+  where+    a = a --+    b = b++letNoBlank =+    let a = a --+        b = b+     in c
+ data/examples/other/comment-trailing-no-blank-line-out.hs view
@@ -0,0 +1,25 @@+noBlankAfterComment = do+  a --+  b++noBlankTextComment = do+  a -- some text+  b++noBlankBlockComment = do+  a {- foo -}+  b++blockCommentFollowedByExpr = do+  a {- foo -} 1+  b++whereNoBlank = foo+  where+    a = a --+    b = b++letNoBlank =+  let a = a --+      b = b+   in c
+ data/examples/other/comment-trailing-no-blank-line.hs view
@@ -0,0 +1,25 @@+noBlankAfterComment = do+  a --+  b++noBlankTextComment = do+  a -- some text+  b++noBlankBlockComment = do+  a {- foo -}+  b++blockCommentFollowedByExpr = do+  a {- foo -} 1+  b++whereNoBlank = foo+  where+    a = a --+    b = b++letNoBlank =+  let a = a --+      b = b+   in c
+ data/examples/other/comment-trigger-escaping-four-out.hs view
@@ -0,0 +1,15 @@+-- Excessive backslashes in multi line:+{-+*+|+-}++test = do+    -- Maybe excessive in single line:+    -- \* (if there's nothing after the * the line is completely dropped)+    line1+    -- Maybe excessive in multi line:+    {- \| is this excessive?+    * no excessive here at least+    -}+    line2
+ data/examples/other/comment-trigger-escaping-out.hs view
@@ -0,0 +1,15 @@+-- Excessive backslashes in multi line:+{-+*+|+-}++test = do+  -- Maybe excessive in single line:+  -- \* (if there's nothing after the * the line is completely dropped)+  line1+  -- Maybe excessive in multi line:+  {- \| is this excessive?+  * no excessive here at least+  -}+  line2
+ data/examples/other/comment-trigger-escaping.hs view
@@ -0,0 +1,15 @@+-- Excessive backslashes in multi line:+{-+*+|+-}++test = do+  -- Maybe excessive in single line:+  -- * (if there's nothing after the * the line is completely dropped)+  line1+  -- Maybe excessive in multi line:+  {- | is this excessive?+  * no excessive here at least+  -}+  line2
data/examples/other/comment-two-blocks-four-out.hs view
@@ -2,7 +2,8 @@ newNames =     let (*) = flip (,)      in [ "Control" * "Monad"-    -- Foo -    -- Bar+        -- Foo++        -- Bar         ]
data/examples/other/comment-two-blocks-out.hs view
@@ -2,7 +2,8 @@ newNames =   let (*) = flip (,)    in [ "Control" * "Monad"-  -- Foo -  -- Bar+      -- Foo++      -- Bar       ]
data/examples/other/empty-forall-four-out.hs view
@@ -12,7 +12,7 @@     forall. T x = x  {-# RULES-"r"+"r" forall.     r a =         ()     #-}
data/examples/other/empty-forall-out.hs view
@@ -12,7 +12,7 @@   forall. T x = x  {-# RULES-"r"+"r" forall.   r a =     ()   #-}
data/examples/other/empty-haddock-four-out.hs view
@@ -1,9 +1,13 @@+-- \| module Test (+    -- \|     test, ) where +-- \| test ::+    -- \|     test -data T = T+data T = T {- \^ -}
data/examples/other/empty-haddock-out.hs view
@@ -1,9 +1,13 @@+-- \| module Test-  ( test,+  ( -- \|+    test,   ) where +-- \| test ::+  -- \|   test -data T = T+data T = T {- \^ -}
data/examples/other/invalid-haddock-weird-four-out.hs view
@@ -1,5 +1,3 @@ {-# LANGUAGE TemplateHaskell #-} -foo = foo---- \|# ${+foo = foo -- \|# ${
data/examples/other/invalid-haddock-weird-out.hs view
@@ -1,5 +1,3 @@ {-# LANGUAGE TemplateHaskell #-} -foo = foo---- \|# ${+foo = foo -- \|# ${
+ data/examples/other/pragma-below-header-four-out.hs view
@@ -0,0 +1,19 @@+module Plugin.Data.Spec where++{-+foo bar boz+-}+monoConstructor :: Int+monoConstructor = 1++-- hello world++-- | A type of rose trees with empty leaves.+data EmptyRose = EmptyRose [EmptyRose]++-- This seems to cause issue+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}++-- bob alice eve+f :: ()+f = ()
+ data/examples/other/pragma-below-header-out.hs view
@@ -0,0 +1,19 @@+module Plugin.Data.Spec where++{-+foo bar boz+-}+monoConstructor :: Int+monoConstructor = 1++-- hello world++-- | A type of rose trees with empty leaves.+data EmptyRose = EmptyRose [EmptyRose]++-- This seems to cause issue+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}++-- bob alice eve+f :: ()+f = ()
+ data/examples/other/pragma-below-header.hs view
@@ -0,0 +1,18 @@+module Plugin.Data.Spec where++{-+foo bar boz+-}+monoConstructor :: Int+monoConstructor = 1++-- hello world+-- | A type of rose trees with empty leaves.+data EmptyRose = EmptyRose [EmptyRose]++-- This seems to cause issue+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}++-- bob alice eve+f :: ()+f = ()
+ data/examples/other/pragma-comment-multi-extension-four-out.hs view
@@ -0,0 +1,5 @@+-- comment+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}++module Foo where
+ data/examples/other/pragma-comment-multi-extension-out.hs view
@@ -0,0 +1,5 @@+-- comment+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}++module Foo where
+ data/examples/other/pragma-comment-multi-extension.hs view
@@ -0,0 +1,4 @@+-- comment+{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}++module Foo where
+ data/fourmolu/haddock-style/output-auto-module=auto.hs view
@@ -0,0 +1,38 @@+-- | This is a test multiline+-- module haddock+module Foo where++-- | This is a singleline function haddock+single1 :: Int++{- | This is a singleline function haddock -}+single2 :: Int++-- | This is a multiline+-- function haddock+multi1 :: Int++{- |+This is a multiline+function haddock+-}+multi2 :: Int++{- | This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++-- | This is a haddock+--+-- with two consecutive newlines+--+--+-- https://github.com/fourmolu/fourmolu/issues/172+foo :: Int+foo = 42++-- | This is a haddock containing another haddock+--+-- > {-# LANGUAGE ScopedTypeVariables #-}+haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-auto-module=multi_line.hs view
@@ -0,0 +1,39 @@+{- | This is a test multiline+module haddock+-}+module Foo where++-- | This is a singleline function haddock+single1 :: Int++{- | This is a singleline function haddock -}+single2 :: Int++-- | This is a multiline+-- function haddock+multi1 :: Int++{- |+This is a multiline+function haddock+-}+multi2 :: Int++{- | This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++-- | This is a haddock+--+-- with two consecutive newlines+--+--+-- https://github.com/fourmolu/fourmolu/issues/172+foo :: Int+foo = 42++-- | This is a haddock containing another haddock+--+-- > {-# LANGUAGE ScopedTypeVariables #-}+haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-auto-module=multi_line_compact.hs view
@@ -0,0 +1,39 @@+{-| This is a test multiline+module haddock+-}+module Foo where++-- | This is a singleline function haddock+single1 :: Int++{- | This is a singleline function haddock -}+single2 :: Int++-- | This is a multiline+-- function haddock+multi1 :: Int++{- |+This is a multiline+function haddock+-}+multi2 :: Int++{- | This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++-- | This is a haddock+--+-- with two consecutive newlines+--+--+-- https://github.com/fourmolu/fourmolu/issues/172+foo :: Int+foo = 42++-- | This is a haddock containing another haddock+--+-- > {-# LANGUAGE ScopedTypeVariables #-}+haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-auto-module=single_line.hs view
@@ -0,0 +1,38 @@+-- | This is a test multiline+-- module haddock+module Foo where++-- | This is a singleline function haddock+single1 :: Int++{- | This is a singleline function haddock -}+single2 :: Int++-- | This is a multiline+-- function haddock+multi1 :: Int++{- |+This is a multiline+function haddock+-}+multi2 :: Int++{- | This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++-- | This is a haddock+--+-- with two consecutive newlines+--+--+-- https://github.com/fourmolu/fourmolu/issues/172+foo :: Int+foo = 42++-- | This is a haddock containing another haddock+--+-- > {-# LANGUAGE ScopedTypeVariables #-}+haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-auto.hs view
@@ -0,0 +1,38 @@+-- | This is a test multiline+-- module haddock+module Foo where++-- | This is a singleline function haddock+single1 :: Int++{- | This is a singleline function haddock -}+single2 :: Int++-- | This is a multiline+-- function haddock+multi1 :: Int++{- |+This is a multiline+function haddock+-}+multi2 :: Int++{- | This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++-- | This is a haddock+--+-- with two consecutive newlines+--+--+-- https://github.com/fourmolu/fourmolu/issues/172+foo :: Int+foo = 42++-- | This is a haddock containing another haddock+--+-- > {-# LANGUAGE ScopedTypeVariables #-}+haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-multi_line-module=auto.hs view
@@ -0,0 +1,41 @@+-- | This is a test multiline+-- module haddock+module Foo where++-- | This is a singleline function haddock+single1 :: Int++-- | This is a singleline function haddock+single2 :: Int++{- | This is a multiline+function haddock+-}+multi1 :: Int++{- |+This is a multiline+function haddock+-}+multi2 :: Int++{- | This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++{- | This is a haddock++with two consecutive newlines+++https://github.com/fourmolu/fourmolu/issues/172+-}+foo :: Int+foo = 42++{- | This is a haddock containing another haddock++> {-# LANGUAGE ScopedTypeVariables #-}+-}+haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line-module=multi_line.hs view
@@ -37,6 +37,6 @@  {- | This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line-module=multi_line_compact.hs view
@@ -37,6 +37,6 @@  {- | This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line-module=single_line.hs view
@@ -36,6 +36,6 @@  {- | This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line.hs view
@@ -37,6 +37,6 @@  {- | This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-multi_line_compact-module=auto.hs view
@@ -0,0 +1,41 @@+-- | This is a test multiline+-- module haddock+module Foo where++-- | This is a singleline function haddock+single1 :: Int++-- | This is a singleline function haddock+single2 :: Int++{-| This is a multiline+function haddock+-}+multi1 :: Int++{-|+This is a multiline+function haddock+-}+multi2 :: Int++{-| This is a multiline haddock+     with indentation+-}+multi_indentation :: Int++{-| This is a haddock++with two consecutive newlines+++https://github.com/fourmolu/fourmolu/issues/172+-}+foo :: Int+foo = 42++{-| This is a haddock containing another haddock++> {-# LANGUAGE ScopedTypeVariables #-}+-}+haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line_compact-module=multi_line.hs view
@@ -37,6 +37,6 @@  {-| This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line_compact-module=multi_line_compact.hs view
@@ -37,6 +37,6 @@  {-| This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line_compact-module=single_line.hs view
@@ -36,6 +36,6 @@  {-| This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
data/fourmolu/haddock-style/output-multi_line_compact.hs view
@@ -37,6 +37,6 @@  {-| This is a haddock containing another haddock -> {\-# LANGUAGE ScopedTypeVariables #-\}+> {-# LANGUAGE ScopedTypeVariables #-} -} haddock_in_haddock :: Int
+ data/fourmolu/haddock-style/output-single_line-module=auto.hs view
@@ -0,0 +1,36 @@+-- | This is a test multiline+-- module haddock+module Foo where++-- | This is a singleline function haddock+single1 :: Int++-- | This is a singleline function haddock+single2 :: Int++-- | This is a multiline+-- function haddock+multi1 :: Int++-- |+-- This is a multiline+-- function haddock+multi2 :: Int++-- | This is a multiline haddock+--      with indentation+multi_indentation :: Int++-- | This is a haddock+--+-- with two consecutive newlines+--+--+-- https://github.com/fourmolu/fourmolu/issues/172+foo :: Int+foo = 42++-- | This is a haddock containing another haddock+--+-- > {-# LANGUAGE ScopedTypeVariables #-}+haddock_in_haddock :: Int
data/fourmolu/import-grouping/input.hs view
@@ -1,13 +1,20 @@ module Main where +import Data.Either import Data.Text (Text) import Data.Maybe (maybe)  import qualified Data.Text import Control.Monad (Monad (..))+import Data.Functor  import qualified System.IO as SIO import SomeInternal.Module1 (anotherDefinition, someDefinition) import qualified SomeInternal.Module2 as Mod2 import Text.Printf (printf) import qualified SomeModule+import SomeInternal.Module2++import Foo+-- some comment+import Foo.Bar
data/fourmolu/import-grouping/output-by_qualified.hs view
@@ -1,9 +1,15 @@ module Main where  import Control.Monad (Monad (..))+import Data.Either+import Data.Functor import Data.Maybe (maybe) import Data.Text (Text)+import Foo+-- some comment+import Foo.Bar import SomeInternal.Module1 (anotherDefinition, someDefinition)+import SomeInternal.Module2 import Text.Printf (printf)  import qualified Data.Text
data/fourmolu/import-grouping/output-by_scope.hs view
@@ -1,12 +1,18 @@ module Main where  import Control.Monad (Monad (..))+import Data.Either+import Data.Functor import Data.Maybe (maybe) import Data.Text (Text) import qualified Data.Text+import Foo+-- some comment+import Foo.Bar import qualified SomeModule import qualified System.IO as SIO import Text.Printf (printf)  import SomeInternal.Module1 (anotherDefinition, someDefinition)+import SomeInternal.Module2 import qualified SomeInternal.Module2 as Mod2
data/fourmolu/import-grouping/output-by_scope_then_qualified.hs view
@@ -1,8 +1,13 @@ module Main where  import Control.Monad (Monad (..))+import Data.Either+import Data.Functor import Data.Maybe (maybe) import Data.Text (Text)+import Foo+-- some comment+import Foo.Bar import Text.Printf (printf)  import qualified Data.Text@@ -10,5 +15,6 @@ import qualified System.IO as SIO  import SomeInternal.Module1 (anotherDefinition, someDefinition)+import SomeInternal.Module2  import qualified SomeInternal.Module2 as Mod2
data/fourmolu/import-grouping/output-custom.hs view
@@ -1,5 +1,12 @@ module Main where +import Data.Either+import Data.Functor+import Foo+-- some comment+import Foo.Bar+import SomeInternal.Module2+ import Data.Text (Text) import qualified Data.Text 
data/fourmolu/import-grouping/output-preserve.hs view
@@ -1,13 +1,20 @@ module Main where +import Data.Either import Data.Maybe (maybe) import Data.Text (Text)  import Control.Monad (Monad (..))+import Data.Functor import qualified Data.Text  import SomeInternal.Module1 (anotherDefinition, someDefinition)+import SomeInternal.Module2 import qualified SomeInternal.Module2 as Mod2 import qualified SomeModule import qualified System.IO as SIO import Text.Printf (printf)++import Foo+-- some comment+import Foo.Bar
data/fourmolu/import-grouping/output-single.hs view
@@ -1,10 +1,16 @@ module Main where  import Control.Monad (Monad (..))+import Data.Either+import Data.Functor import Data.Maybe (maybe) import Data.Text (Text) import qualified Data.Text+import Foo+-- some comment+import Foo.Bar import SomeInternal.Module1 (anotherDefinition, someDefinition)+import SomeInternal.Module2 import qualified SomeInternal.Module2 as Mod2 import qualified SomeModule import qualified System.IO as SIO
data/fourmolu/indentation/input.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (   asdf,   bar,@@ -21,3 +23,7 @@  class Foo a where   foo :: a -> Int++pattern HeadC x <- x : xs+  where+    HeadC x = [x]
data/fourmolu/indentation/output-2-indent_wheres.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (   asdf,   bar,@@ -21,3 +23,7 @@  class Foo a where   foo :: a -> Int++pattern HeadC x <- x : xs+  where+    HeadC x = [x]
data/fourmolu/indentation/output-2.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (   asdf,   bar,@@ -21,3 +23,7 @@  class Foo a where   foo :: a -> Int++pattern HeadC x <- x : xs+ where+  HeadC x = [x]
data/fourmolu/indentation/output-3-indent_wheres.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (    asdf,    bar,@@ -21,3 +23,7 @@  class Foo a where    foo :: a -> Int++pattern HeadC x <- x : xs+   where+      HeadC x = [x]
data/fourmolu/indentation/output-3.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (    asdf,    bar,@@ -21,3 +23,7 @@  class Foo a where    foo :: a -> Int++pattern HeadC x <- x : xs+  where+   HeadC x = [x]
data/fourmolu/indentation/output-4-indent_wheres.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (     asdf,     bar,@@ -21,3 +23,7 @@  class Foo a where     foo :: a -> Int++pattern HeadC x <- x : xs+    where+        HeadC x = [x]
data/fourmolu/indentation/output-4.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE PatternSynonyms #-}+ module Foo (     asdf,     bar,@@ -21,3 +23,7 @@  class Foo a where     foo :: a -> Int++pattern HeadC x <- x : xs+  where+    HeadC x = [x]
+ data/fourmolu/record-style/input.hs view
@@ -0,0 +1,24 @@+data Foo = Foo+  { a :: Int+  , b :: Int+  , c :: Int+  }++-- Test nesting+data Client = PremiumClient { name :: String,+    address :: String, aFieldWithAnUnneccessarilyLongFieldName :: String }+  -- And some comment here as well+  | NotSoPremiumClient { name :: String,+    address :: String, aFieldWithAnUnneccessarilyLongFieldName :: String }++x :: Foo -> [Int]+x Foo{ a = possiblyLongName,+    b = anotherLongName,+    c = longNameAsWell+    } = [possiblyLongName, anotherLongName, longNameAsWell]++y :: Int -> Int -> Int -> Foo+y possiblyLongName anotherLongName longNameAsWell = Foo{ a = possiblyLongName,+    b = anotherLongName,+    c = longNameAsWell+  }
+ data/fourmolu/record-style/output-aligned-leading.hs view
@@ -0,0 +1,35 @@+data Foo = Foo+    { a :: Int+    , b :: Int+    , c :: Int+    }++-- Test nesting+data Client+    = PremiumClient+        { name :: String+        , address :: String+        , aFieldWithAnUnneccessarilyLongFieldName :: String+        }+    | -- And some comment here as well+      NotSoPremiumClient+        { name :: String+        , address :: String+        , aFieldWithAnUnneccessarilyLongFieldName :: String+        }++x :: Foo -> [Int]+x+    Foo+        { a = possiblyLongName+        , b = anotherLongName+        , c = longNameAsWell+        } = [possiblyLongName, anotherLongName, longNameAsWell]++y :: Int -> Int -> Int -> Foo+y possiblyLongName anotherLongName longNameAsWell =+    Foo+        { a = possiblyLongName+        , b = anotherLongName+        , c = longNameAsWell+        }
+ data/fourmolu/record-style/output-aligned-trailing.hs view
@@ -0,0 +1,35 @@+data Foo = Foo+    { a :: Int,+      b :: Int,+      c :: Int+    }++-- Test nesting+data Client+    = PremiumClient+        { name :: String,+          address :: String,+          aFieldWithAnUnneccessarilyLongFieldName :: String+        }+    | -- And some comment here as well+      NotSoPremiumClient+        { name :: String,+          address :: String,+          aFieldWithAnUnneccessarilyLongFieldName :: String+        }++x :: Foo -> [Int]+x+    Foo+        { a = possiblyLongName,+          b = anotherLongName,+          c = longNameAsWell+        } = [possiblyLongName, anotherLongName, longNameAsWell]++y :: Int -> Int -> Int -> Foo+y possiblyLongName anotherLongName longNameAsWell =+    Foo+        { a = possiblyLongName,+          b = anotherLongName,+          c = longNameAsWell+        }
+ data/fourmolu/record-style/output-knr-leading.hs view
@@ -0,0 +1,35 @@+data Foo = Foo {+    a :: Int+    , b :: Int+    , c :: Int+    }++-- Test nesting+data Client+    = PremiumClient {+        name :: String+        , address :: String+        , aFieldWithAnUnneccessarilyLongFieldName :: String+        }+    | -- And some comment here as well+      NotSoPremiumClient {+        name :: String+        , address :: String+        , aFieldWithAnUnneccessarilyLongFieldName :: String+        }++x :: Foo -> [Int]+x+    Foo {+        a = possiblyLongName+        , b = anotherLongName+        , c = longNameAsWell+        } = [possiblyLongName, anotherLongName, longNameAsWell]++y :: Int -> Int -> Int -> Foo+y possiblyLongName anotherLongName longNameAsWell =+    Foo {+        a = possiblyLongName+        , b = anotherLongName+        , c = longNameAsWell+        }
+ data/fourmolu/record-style/output-knr-trailing.hs view
@@ -0,0 +1,35 @@+data Foo = Foo {+    a :: Int,+    b :: Int,+    c :: Int+}++-- Test nesting+data Client+    = PremiumClient {+        name :: String,+        address :: String,+        aFieldWithAnUnneccessarilyLongFieldName :: String+    }+    | -- And some comment here as well+      NotSoPremiumClient {+        name :: String,+        address :: String,+        aFieldWithAnUnneccessarilyLongFieldName :: String+    }++x :: Foo -> [Int]+x+    Foo {+        a = possiblyLongName,+        b = anotherLongName,+        c = longNameAsWell+    } = [possiblyLongName, anotherLongName, longNameAsWell]++y :: Int -> Int -> Int -> Foo+y possiblyLongName anotherLongName longNameAsWell =+    Foo {+        a = possiblyLongName,+        b = anotherLongName,+        c = longNameAsWell+    }
data/fourmolu/sort-deriving-clauses/input.hs view
@@ -7,6 +7,6 @@   deriving (ToJSON)  data B -  -- A comment that will end up in an odd place+  -- A comment that will stay above Show   deriving stock (Show)   deriving (Eq)
data/fourmolu/sort-deriving-clauses/output-False.hs view
@@ -7,6 +7,6 @@     deriving (ToJSON)  data B-    -- A comment that will end up in an odd place+    -- A comment that will stay above Show     deriving stock (Show)     deriving (Eq)
data/fourmolu/sort-deriving-clauses/output-True.hs view
@@ -7,7 +7,6 @@     deriving newtype (Num)  data B-    -- A comment that will end up in an odd place-     deriving (Eq)+    -- A comment that will stay above Show     deriving stock (Show)
data/fourmolu/trailing-section-operators/output-True.hs view
@@ -3,11 +3,11 @@ someShortFunction $ someShortFunction $ \x -> do     putStrLn x -someLongFunction $-    someLongFunction $-        someLongFunction $-            someLongFunction $-                someLongFunction x+someLongFunction+    $ someLongFunction+    $ someLongFunction+    $ someLongFunction+    $ someLongFunction x  someLongFunction $     someLongFunction $
data/fourmolu/unicode-syntax/output-always.hs view
@@ -55,8 +55,8 @@ deconstruct (MkT1 x) = x  pattern HeadC x ← x : xs-    where-        HeadC x = [x]+  where+    HeadC x = [x]  pattern Point ∷ Int → Int → (Int, Int) pattern Point{x, y} = (x, y)
data/fourmolu/unicode-syntax/output-detect.hs view
@@ -55,8 +55,8 @@ deconstruct (MkT1 x) = x  pattern HeadC x ← x : xs-    where-        HeadC x = [x]+  where+    HeadC x = [x]  pattern Point ∷ Int → Int → (Int, Int) pattern Point{x, y} = (x, y)
data/fourmolu/unicode-syntax/output-never.hs view
@@ -55,8 +55,8 @@ deconstruct (MkT1 x) = x  pattern HeadC x <- x : xs-    where-        HeadC x = [x]+  where+    HeadC x = [x]  pattern Point :: Int -> Int -> (Int, Int) pattern Point{x, y} = (x, y)
extract-hackage-info/hackage-info.bin view

binary file changed (1002933 → 1243493 bytes)

fixity-tests/test-1-no-extra-info-expected.hs view
@@ -1,12 +1,12 @@ lenses =-  Just $-    M.fromList $-      "type"-        .= ("user.connection" :: Text)-        # "connection"-        .= uc-        # "user"-        .= case name of-          Just n -> Just $ object ["name" .= n]-          Nothing -> Nothing-        # []+  Just+    $ M.fromList+    $ "type"+      .= ("user.connection" :: Text)+      # "connection"+      .= uc+      # "user"+      .= case name of+        Just n -> Just $ object ["name" .= n]+        Nothing -> Nothing+      # []
fixity-tests/test-1-with-fixity-info-expected.hs view
@@ -1,9 +1,9 @@ lenses =-  Just $-    M.fromList $-      "type" .= ("user.connection" :: Text)-        # "connection" .= uc-        # "user" .= case name of-          Just n -> Just $ object ["name" .= n]-          Nothing -> Nothing-        # []+  Just+    $ M.fromList+    $ "type" .= ("user.connection" :: Text)+      # "connection" .= uc+      # "user" .= case name of+        Just n -> Just $ object ["name" .= n]+        Nothing -> Nothing+      # []
fourmolu.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: fourmolu-version: 0.19.0.1+version: 0.21.0.0 license: BSD-3-Clause license-file: LICENSE.md maintainer:@@ -8,9 +8,9 @@     George Thomas <georgefsthomas@gmail.com>     Brandon Chinn <brandonchinn178@gmail.com> tested-with:-  ghc ==9.8.4-  ghc ==9.10.1-  ghc ==9.12.1+  ghc ==9.10.3+  ghc ==9.12.4+  ghc ==9.14.1  homepage: https://github.com/fourmolu/fourmolu bug-reports: https://github.com/fourmolu/fourmolu/issues@@ -44,6 +44,9 @@ library   exposed-modules:     Ormolu+    Ormolu.Comments.Anchor+    Ormolu.Comments.Invariants+    Ormolu.Comments.Tree     Ormolu.Config     Ormolu.Diff.ParseResult     Ormolu.Diff.Text@@ -60,6 +63,7 @@     Ormolu.Parser.Result     Ormolu.Printer     Ormolu.Printer.Combinators+    Ormolu.Printer.CommentPlacement     Ormolu.Printer.Comments     Ormolu.Printer.Internal     Ormolu.Printer.Meat.Common@@ -84,8 +88,8 @@     Ormolu.Printer.Meat.Module     Ormolu.Printer.Meat.Pragma     Ormolu.Printer.Meat.Type+    Ormolu.Printer.Meat.Type.Function     Ormolu.Printer.Operators-    Ormolu.Printer.SpanStream     Ormolu.Processing.Common     Ormolu.Processing.Cpp     Ormolu.Processing.Preprocess@@ -100,8 +104,8 @@   other-modules: GHC.DynFlags   default-language: GHC2021   build-depends:-    Cabal-syntax >=3.14 && <3.15,-    Diff >=0.4 && <2,+    Cabal-syntax >=3.16 && <3.17,+    Diff >=0.4 && <2.0 || >=2.0.1 && <2.1,     MemoTrie >=0.6 && <0.7,     ansi-terminal >=0.10 && <1.2,     array >=0.5 && <0.6,@@ -109,11 +113,11 @@     binary >=0.8 && <0.9,     bytestring >=0.2 && <0.13,     choice >=0.2.4.1 && <0.3,-    containers >=0.5 && <0.8,+    containers >=0.5 && <0.9,     directory ^>=1.3,     file-embed >=0.0.15 && <0.1,     filepath >=1.2 && <1.6,-    ghc-lib-parser >=9.12 && <9.13,+    ghc-lib-parser >=9.14 && <9.15,     megaparsec >=9,     mtl >=2 && <3,     syb >=0.7 && <0.8,@@ -140,6 +144,8 @@       -Wredundant-constraints       -Wpartial-fields       -Wunused-packages+      -haddock+      -Winvalid-haddock   else     ghc-options:       -O2@@ -152,13 +158,13 @@   autogen-modules: Paths_fourmolu   default-language: GHC2021   build-depends:-    Cabal-syntax >=3.14 && <3.15,+    Cabal-syntax >=3.16 && <3.17,     base >=4.12 && <5,-    containers >=0.5 && <0.8,+    containers >=0.5 && <0.9,     directory ^>=1.3,     filepath >=1.2 && <1.6,-    ghc-lib-parser >=9.12 && <9.13,-    optparse-applicative >=0.14 && <0.19,+    ghc-lib-parser >=9.14 && <9.15,+    optparse-applicative >=0.14 && <0.20,     text >=2.1 && <3,     th-env >=0.1.1 && <0.2,     unliftio >=0.2.10 && <0.3,@@ -186,6 +192,8 @@       -Wpartial-fields       -Wunused-packages       -Wwarn=unused-packages+      -haddock+      -Winvalid-haddock   else     ghc-options:       -O2@@ -198,6 +206,7 @@   hs-source-dirs: tests   other-modules:     Ormolu.CabalInfoSpec+    Ormolu.Comments.AnchorSpec     Ormolu.Diff.TextSpec     Ormolu.Fixity.ParserSpec     Ormolu.Fixity.PrinterSpec@@ -207,17 +216,18 @@     Ormolu.Parser.ParseFailureSpec     Ormolu.Parser.PragmaSpec     Ormolu.PrinterSpec+    Ormolu.TestConfig    default-language: GHC2021   build-depends:-    Cabal-syntax >=3.14 && <3.15,+    Cabal-syntax >=3.16 && <3.17,     QuickCheck >=2.14,     base >=4.14 && <5,     choice >=0.2.4.1 && <0.3,-    containers >=0.5 && <0.8,+    containers >=0.5 && <0.9,     directory ^>=1.3,     filepath >=1.2 && <1.6,-    ghc-lib-parser >=9.12 && <9.13,+    ghc-lib-parser >=9.14 && <9.15,     hspec >=2 && <3,     hspec-megaparsec >=2.2,     megaparsec >=9,@@ -252,6 +262,8 @@       -Wredundant-constraints       -Wpartial-fields       -Wunused-packages+      -haddock+      -Winvalid-haddock   else     ghc-options:       -O2
fourmolu.yaml view
@@ -5,12 +5,13 @@ column-limit: none function-arrows: trailing comma-style: trailing+record-style: aligned import-export-style: trailing import-grouping: legacy indent-wheres: true record-brace-space: true newlines-between-decls: 1-haddock-style: single-line+haddock-style: auto haddock-style-module: null haddock-location-signature: auto let-style: inline
src/GHC/DynFlags.hs view
@@ -7,10 +7,12 @@   ) where +import GHC.Data.FastString import GHC.Driver.Session import GHC.Platform import GHC.Settings import GHC.Settings.Config+import GHC.Unit.Types import GHC.Utils.Fingerprint  fakeSettings :: Settings@@ -43,6 +45,10 @@             platform_constants = Nothing           },       sPlatformMisc = PlatformMisc {},+      sUnitSettings =+        UnitSettings+          { unitSettings_baseUnitId = UnitId $ fsLit "ormolu"+          },       sToolSettings =         ToolSettings           { toolSettings_opt_P_fingerprint = fingerprint0,
src/Ormolu.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE RecordWildCards #-}  -- | A formatter for Haskell source code. This module exposes the official--- stable API, other modules may be not as reliable.+-- stable API; other modules may not be as reliable. module Ormolu   ( -- * Top-level formatting functions     ormolu,@@ -47,6 +47,7 @@ import Control.Exception import Control.Monad import Control.Monad.IO.Class (MonadIO (..))+import Data.Choice qualified as Choice import Data.Map.Strict qualified as Map import Data.Maybe (fromMaybe) import Data.Set qualified as Set@@ -55,9 +56,11 @@ import Data.Text.IO.Utf8 qualified as T.Utf8 import Debug.Trace import GHC.Driver.Errors.Types+import GHC.Hs (HsModule (..), locA) import GHC.Types.Error import GHC.Types.SrcLoc import GHC.Utils.Error+import Ormolu.Comments.Invariants import Ormolu.Config import Ormolu.Diff.ParseResult import Ormolu.Diff.Text@@ -73,12 +76,12 @@  -- | Format a 'Text'. ----- The function+-- The function: -----     * Needs 'IO' because some functions from GHC that are necessary to---       setup parsing context require 'IO'. There should be no visible---       side-effects though.---     * Takes file name just to use it in parse error messages.+--     * Needs 'IO' because some GHC functions that are necessary to set up+--       the parsing context require 'IO'. There should be no visible+--       side effects, though.+--     * Takes a file name only to use it in parse error messages. --     * Throws 'OrmoluException'. -- -- __NOTE__: The caller is responsible for setting the appropriate value in@@ -113,15 +116,44 @@         forM_ comments $ \(L loc comment) ->           traceM $ unwords ["*** COMMENT ***", showOutputable loc, show comment]       _ -> pure ()-  -- We're forcing 'formattedText' here because otherwise errors (such as-  -- messages about not-yet-supported functionality) will be thrown later-  -- when we try to parse the rendered code back, inside of GHC monad-  -- wrapper which will lead to error messages presenting the exceptions as-  -- GHC bugs.-  let !formattedText = printSnippets (cfgDebug cfg) result0 $ cfgPrinterOpts cfgWithIndices+  -- We force 'formattedText' here because otherwise errors (such as+  -- messages about not-yet-supported functionality) would be thrown later,+  -- when we try to parse the rendered code back inside the GHC monad+  -- wrapper, which would lead to error messages presenting the exceptions+  -- as GHC bugs.+  let printed =+        printSnippetsWithPlacements+          (cfgPrinterOpts cfgWithIndices)+          (Choice.fromBool (cfgDebug cfg))+          result0+      !formattedText = T.concat (fst <$> printed)+  -- Every comment of the input should come out exactly once, and in the+  -- order it went in. The AST check below does not cover this: it compares+  -- the comment streams as multisets, and the comments that travel with+  -- pragmas are not in the stream at all.+  unless (cfgUnsafe cfg) . liftIO $ do+    let violations =+          concat+            [ checkCommentInvariants+                (getLoc <$> inputComments r)+                (reorderableSpans (prParsedSource r))+                placements+            | (ParsedSnippet r, (_, placements)) <- result0 `zip` printed+            ]+        -- Imports are sorted and merged, so a comment attached to one of+        -- them may legitimately come out in a different order than it went+        -- in.+        reorderableSpans hsmod =+          [ spn+          | L l _ <- hsmodImports hsmod,+            Just spn <- [srcSpanToRealSrcSpan (locA l)]+          ]+    unless (null violations) $+      throwIO (OrmoluCommentInvariantsViolated path violations)   when (not (cfgUnsafe cfg) || cfgCheckIdempotence cfg) $ do-    -- Parse the result of pretty-printing again and make sure that AST-    -- is the same as AST of original snippet module span positions.+    -- Parse the result of pretty-printing again and make sure that its AST+    -- is the same as the AST of the original snippet, modulo span+    -- positions.     (_, result1) <-       parseModule'         cfg@@ -144,7 +176,11 @@     -- Try re-formatting the formatted result to check if we get exactly     -- the same output.     when (cfgCheckIdempotence cfg) . liftIO $-      let reformattedText = printSnippets (cfgDebug cfg) result1 $ cfgPrinterOpts cfgWithIndices+      let reformattedText =+            printSnippets+              (cfgPrinterOpts cfgWithIndices)+              (Choice.fromBool (cfgDebug cfg))+              result1        in case diffText formattedText reformattedText path of             Nothing -> return ()             Just diff -> throwIO (OrmoluNonIdempotentOutput diff)@@ -181,11 +217,11 @@ ormoluStdin cfg =   liftIO T.Utf8.getContents >>= ormolu cfg "<stdin>" --- | Refine a 'Config' by incorporating given 'SourceType', 'CabalInfo', and--- fixity overrides 'FixityMap'. You can use 'detectSourceType' to deduce--- 'SourceType' based on the file extension,--- 'CabalUtils.getCabalInfoForSourceFile' to obtain 'CabalInfo' and--- 'getFixityOverridesForSourceFile' for 'FixityMap'.+-- | Refine a 'Config' by incorporating the given 'SourceType', 'CabalInfo',+-- and fixity overrides 'FixityMap'. You can use 'detectSourceType' to deduce+-- the 'SourceType' from the file extension,+-- 'CabalUtils.getCabalInfoForSourceFile' to obtain the 'CabalInfo', and+-- 'getFixityOverridesForSourceFile' for the 'FixityMap'. -- -- @since 0.5.3.0 refineConfig ::
+ src/Ormolu/Comments/Anchor.hs view
@@ -0,0 +1,326 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++-- | Positional comment attachment.+--+-- This module decides who owns a comment from where it sits in the source,+-- once, before anything is printed. The answer therefore does not depend on+-- the order in which the printer visits elements, which is what made+-- reordered imports and reassociated operator trees lose comments before.+--+-- The rule is short enough to state in full. Find the element that encloses+-- the comment most tightly. Within that element, find which gap between its+-- children the comment falls into. Then:+--+--   * a comment that starts on the line where the preceding sibling ends+--     trails that sibling;+--   * otherwise, if a sibling follows, the comment goes before it;+--   * otherwise the comment trails the last sibling;+--   * an element with no children at all owns the comment outright.+module Ormolu.Comments.Anchor+  ( CommentAnchor (..),+    attachComments,+    anchorFor,++    -- * Using the anchors while printing+    AnchorMap,+    mkAnchorMap,+    noComments,+    claimBefore,+    commentsBefore,+    claimTrailing,+    claimRemaining,+    pendingComments,+    commentsAnchoredWithin,+  )+where++import Data.List (find, sortOn)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as Map+import Data.Maybe (listToMaybe)+import Data.Set qualified as Set+import GHC.Types.SrcLoc+import Ormolu.Comments.Tree+import Ormolu.Parser.CommentStream++-- | Where a comment belongs.+data CommentAnchor+  = -- | On its own line(s) above the element+    AnchorBefore RealSrcSpan+  | -- | After the element, either on the same line or below it+    AnchorTrailing RealSrcSpan+  | -- | Inside the element, which has no children of its own+    AnchorInside RealSrcSpan+  | -- | Not inside anything: the comment belongs to the module+    AnchorModule+  deriving (Eq, Show)++-- | Attach every comment of a module.+attachComments ::+  -- | Comments, in source order+  [LComment] ->+  -- | Spans of all \"located\" elements of the module+  [RealSrcSpan] ->+  [(LComment, CommentAnchor)]+attachComments comments eltSpans =+  joinBlocks eltSpans [(c, anchorFor forest c) | c <- comments]+  where+    forest = mkSpanForest eltSpans++-- | Make a run of comment lines share one anchor.+--+-- Consecutive lines with nothing but comment between them are one block as+-- far as the reader is concerned, so splitting them across two elements+-- would tear the block apart. The first line decides where the whole block+-- goes.+joinBlocks ::+  -- | Spans of all elements, used to tell whether one stands between two+  -- comments+  [RealSrcSpan] ->+  [(LComment, CommentAnchor)] ->+  [(LComment, CommentAnchor)]+joinBlocks eltSpans = go Nothing+  where+    -- Only the start positions matter below, and only whether one of them+    -- falls in a range, so they are held as a set: this runs for every+    -- comment and scanning the module's spans each time is quadratic.+    eltStarts = Set.fromList (realSrcSpanStart <$> eltSpans)++    go _ [] = []+    go previous ((c@(L spn theComment), anchor) : rest) =+      let anchor' = case previous of+            Just (prevSpn, prevAnchor)+              | continues prevSpn -> prevAnchor+            _ -> anchor+          continues prevSpn =+            not (hasAtomsBefore theComment)+              && srcSpanEndLine prevSpn + 1 == srcSpanStartLine spn+              && not (elementBetween prevSpn spn)+       in (c, anchor') : go (Just (spn, anchor')) rest++    -- Consecutive lines are not one block if an element begins between+    -- them. @{- 0x00 -} sniExt@ followed by @{- 0x0a -} groupExt@ is two+    -- blocks, each leading its own element, not one block of two lines. It+    -- is enough for the element to *start* in the gap: in @f $ {-else-} do@+    -- the @do@ block opens on the first comment's line and runs well past+    -- the second, and the comment on the next line belongs inside it rather+    -- than to the block above.+    elementBetween from to =+      case Set.lookupGE (realSrcSpanEnd from) eltStarts of+        Just s -> s <= realSrcSpanStart to+        Nothing -> False++-- | Attach a single comment to the forest of element spans.+anchorFor :: [SpanTree] -> LComment -> CommentAnchor+anchorFor forest (L comment theComment) = go Nothing Nothing forest+  where+    go enclosing outerTrailing trees =+      case find (\t -> stSpan t `containsSpan` comment) trees of+        -- Descend into the element that encloses the comment, so that the+        -- anchor is always as tight as the source allows. Carry down the+        -- code this level has already put on the comment's line: an element+        -- that opens on that line, such as the right-hand side in @f x = --+        -- c@, has nothing of its own before the comment, but the comment+        -- still trails the @x@ one level up.+        --+        -- Only an element that wraps a single thing may carry it. One with+        -- several children is a list of items, and a comment written at the+        -- head of such a list introduces the items rather than trailing+        -- what stands before the bracket. In+        --+        -- > xs ++ [ -- why?+        -- >   a, b ]+        --+        -- the comment must stay inside the brackets; carrying it up would+        -- pull it, and the block of comment lines below it, out of the list.+        Just t+          | [_] <- stChildren t -> go (Just (stSpan t)) trailingHere (stChildren t)+          | otherwise -> go (Just (stSpan t)) Nothing (stChildren t)+        Nothing -> case (precedingSibling, followingSibling) of+          (Just p, _)+            | trailsCodeOn (stSpan p) -> AnchorTrailing (innermostEndingOnLine p)+          -- A comment with an element right after it on the same line and+          -- nothing of its own before it leads that element: a run of @{-+          -- 0x00 -} sniExt@ must not be read as trailing whatever comes+          -- before and pile up in one place. This outranks the code carried+          -- down from an outer level, so that the @{-a-}@ of @x = ({-a-} b,+          -- c)@ stays with @b@ rather than being pulled out to trail the+          -- @x@.+          (_, Just n)+            | startsOnCommentLine (stSpan n) -> AnchorBefore (stSpan n)+          -- Nothing at this level stands before the comment, but an outer+          -- level put code on its line: the comment trails that code. This+          -- is what keeps @f x = -- c@ on one line, the right-hand side+          -- having opened on that line with the comment as its first+          -- content.+          --+          -- Only a line comment may do this. It runs to the end of the line+          -- either way, so trailing an element one level up still renders+          -- it exactly where it was written. A block comment renders in+          -- place instead, and would end up ahead of the tokens that opened+          -- the element it was written inside: the pragma of @corebar = {-#+          -- CORE "bar baz" #-}@ would move before the @=@.+          (Nothing, _)+            | not (isMultilineComment theComment),+              Just p <- outerTrailing ->+                AnchorTrailing (innermostEndingOnLine p)+          (_, Just n) -> AnchorBefore (stSpan n)+          -- A comment after the last child of an element belongs to that+          -- element, but a comment after everything at the top level+          -- belongs to the module: there is nothing it can trail without+          -- being rendered before syntax that preceded it in the input,+          -- such as the @where@ of a module header.+          (Just p, Nothing)+            | Just _ <- enclosing -> AnchorTrailing (stSpan p)+            | otherwise -> AnchorModule+          (Nothing, Nothing) -> maybe AnchorModule AnchorInside enclosing+          where+            followingSibling =+              listToMaybe+                [ t+                | t <- trees,+                  realSrcSpanStart (stSpan t) >= realSrcSpanEnd comment+                ]+            startsOnCommentLine s =+              srcSpanStartLine s == srcSpanEndLine comment+      where+        precedingSibling =+          lastMaybe+            [ t+            | t <- trees,+              realSrcSpanEnd (stSpan t) <= realSrcSpanStart comment+            ]+        trailingHere = case precedingSibling of+          Just p | trailsCodeOn (stSpan p) -> Just p+          _ -> outerTrailing++    -- A comment only trails an element when it really does sit after code+    -- on that line. Checking the line alone is not enough, because the AST+    -- has zero-width spans that happen to share a line with a comment while+    -- standing before it.+    trailsCodeOn s =+      srcSpanEndLine s == srcSpanStartLine comment+        && hasAtomsBefore theComment++    -- A comment that trails a bracketed construct belongs to the innermost+    -- element that ends on its line, not to the bracket: @(x + y) -- c@+    -- attaches to @y@, so that the comment is rendered next to the+    -- expression it was written next to rather than after the closing+    -- bracket.+    innermostEndingOnLine t =+      case lastMaybe (filter (trailsCodeOn . stSpan) (stChildren t)) of+        Nothing -> stSpan t+        Just t' -> innermostEndingOnLine t'++    lastMaybe xs = if null xs then Nothing else Just (last xs)++----------------------------------------------------------------------------+-- Using the anchors while printing++-- | Anchored comments, arranged so that the printer can look them up by the+-- span of the element it is entering or leaving.+--+-- Comments are claimed rather than consumed: the first element with a given+-- span takes them, and every later element with the same span finds+-- nothing. Since several AST nodes routinely share a span, and the printer+-- enters them outermost first, this gives the comment to the outermost of+-- them, which is what one wants—a comment belongs outside the parentheses,+-- not inside them.+data AnchorMap = AnchorMap+  { amBefore :: Map RealSrcSpan [LComment],+    amTrailing :: Map RealSrcSpan [LComment],+    amModule :: [LComment]+  }++-- | An empty map, for the first of the two rendering passes: it collects+-- the spans of the elements the printer enters, and emits no comments.+noComments :: AnchorMap+noComments =+  AnchorMap {amBefore = Map.empty, amTrailing = Map.empty, amModule = []}++-- | Arrange anchored comments for lookup.+--+-- __NOTE__: 'AnchorInside' is currently folded into 'AnchorTrailing'. Doing+-- it properly needs a combinator for elements that can have no children at+-- all.+mkAnchorMap :: [(LComment, CommentAnchor)] -> AnchorMap+mkAnchorMap anchored =+  AnchorMap+    { amBefore = collect [(spn, c) | (c, AnchorBefore spn) <- anchored],+      amTrailing =+        collect $+          [(spn, c) | (c, AnchorTrailing spn) <- anchored]+            <> [(spn, c) | (c, AnchorInside spn) <- anchored],+      amModule = [c | (c, AnchorModule) <- anchored]+    }+  where+    collect = Map.fromListWith (flip (<>)) . fmap (fmap pure)++-- | The comments that go before the element with the given span, without+-- claiming them.+commentsBefore :: RealSrcSpan -> AnchorMap -> [LComment]+commentsBefore spn = Map.findWithDefault [] spn . amBefore++-- | Claim the comments that go before the element with the given span.+claimBefore :: RealSrcSpan -> AnchorMap -> ([LComment], AnchorMap)+claimBefore spn am =+  case Map.lookup spn (amBefore am) of+    Nothing -> ([], am)+    Just cs -> (cs, am {amBefore = Map.delete spn (amBefore am)})++-- | Claim the comments that go after the element with the given span.+claimTrailing :: RealSrcSpan -> AnchorMap -> ([LComment], AnchorMap)+claimTrailing spn am =+  case Map.lookup spn (amTrailing am) of+    Nothing -> ([], am)+    Just cs -> (cs, am {amTrailing = Map.delete spn (amTrailing am)})++-- | Claim everything that is left: the comments that belong to no element,+-- plus anything that was anchored to an element the printer never entered.+claimRemaining :: AnchorMap -> ([LComment], AnchorMap)+claimRemaining am =+  ( pendingComments am,+    AnchorMap {amBefore = Map.empty, amTrailing = Map.empty, amModule = []}+  )++-- | The comments anchored to the element at the given span, or to any+-- element inside it.+--+-- This is the question the layout decision needs to ask. "Which comments+-- are contained in this element" is a different and much coarser one: a+-- comment anywhere in a declaration is contained in it, but is attached to+-- one particular element, and only that element's layout should have to+-- account for it. This runs for every element the printer enters, so it+-- must not walk the whole map. Anchors are 'RealSrcSpan's ordered by start+-- position, and all of a module's spans share a file, so the anchors that+-- could be contained in the region are the contiguous run whose start lies+-- within it. Cutting the map down to that run first makes the cost+-- proportional to the size of the region rather than to the number of+-- comments in the module.+commentsAnchoredWithin :: RealSrcSpan -> AnchorMap -> [LComment]+commentsAnchoredWithin region AnchorMap {..} =+  sortOn getLoc . concat $+    within amBefore <> within amTrailing+  where+    within =+      Map.elems+        . Map.filterWithKey (\anchor _ -> region `containsSpan` anchor)+        . startingWithin++    -- Antitone in map order: as keys ascend their start position never+    -- decreases, so each predicate holds on a prefix and then stops.+    startingWithin =+      fst+        . Map.spanAntitone ((<= realSrcSpanEnd region) . realSrcSpanStart)+        . snd+        . Map.spanAntitone ((< realSrcSpanStart region) . realSrcSpanStart)++-- | Every comment that has not been emitted yet, in source order.+pendingComments :: AnchorMap -> [LComment]+pendingComments AnchorMap {..} =+  sortOn getLoc $+    amModule+      <> concat (Map.elems amBefore)+      <> concat (Map.elems amTrailing)
+ src/Ormolu/Comments/Invariants.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}++-- | Properties that comment handling has to satisfy, and the check that+-- enforces them.+--+-- Every comment of the input should come out exactly once, and in the order+-- it went in. The check runs on every run of Ormolu, alongside the check+-- that the AST is unchanged, and is disabled by the same @--unsafe@ flag.+--+-- This is the half of comment checking that works on where the comments went+-- rather than on what they say. It compares the /spans/ of the comments a+-- module started with against the spans recorded as the printer emitted+-- them, so it can name the comment that was dropped, duplicated, invented+-- or moved.+--+-- It does /not/ look at the text of a comment at all: rendering one with+-- its contents mangled would pass. That is the other half, and it belongs+-- to 'Ormolu.Diff.ParseResult.diffCommentStream', which compares text and+-- ignores position. Neither check subsumes the other and both run by+-- default.+--+-- Haddocks are outside both halves. GHC's parser makes them part of the AST+-- rather than leaving them in the comment stream, so they are neither among+-- the comments a module started with nor in what the text check compares.+-- Losing or duplicating one changes the AST itself, and that is caught by+-- the third check, 'Ormolu.Diff.ParseResult.diffParseResult' comparing the+-- two syntax trees.+module Ormolu.Comments.Invariants+  ( InvariantViolation (..),+    checkCommentInvariants,+    renderInvariantViolation,+  )+where++import Data.List (sort)+import Data.Map.Strict qualified as Map+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Types.SrcLoc+import Ormolu.Printer.CommentPlacement++-- | A way in which the emitted comments failed to correspond to the+-- comments of the input.+data InvariantViolation+  = -- | A comment of the input was never emitted+    CommentDropped RealSrcSpan+  | -- | A comment was emitted more than once, the given number of times+    CommentDuplicated RealSrcSpan Int+  | -- | A comment was emitted that does not correspond to any comment of+    -- the input+    CommentInvented RealSrcSpan+  | -- | A comment was emitted after one that comes later in the input. The+    -- first span is the comment that was emitted too late, the second is+    -- the one it should have preceded.+    CommentReordered RealSrcSpan RealSrcSpan+  deriving (Eq, Show)++-- | Compare the comments of a snippet against the comments that were+-- emitted while rendering it.+checkCommentInvariants ::+  -- | Spans of all the comments the snippet started with+  [RealSrcSpan] ->+  -- | Spans of the elements the formatter is allowed to reorder, so that+  -- the comments travelling with them are exempt from the order check+  [RealSrcSpan] ->+  -- | Placements recorded while rendering it, in the order of emission+  [CommentPlacement] ->+  [InvariantViolation]+checkCommentInvariants inputSpans reorderable placements =+  dropped <> duplicated <> invented <> reordered+  where+    emitted = cpSpan <$> placements+    -- Pragmas and imports are deliberately sorted and the comments attached+    -- to them travel along, so the order they come out in says nothing.+    -- They are still expected to come out exactly once, which is what+    -- catches a comment being duplicated.+    ordered =+      [ spn+      | CommentPlacement {cpSpan = spn, cpSlot} <- placements,+        cpSlot /= SlotPragma,+        not (travelsWithAReorderedElement cpSlot)+      ]+    travelsWithAReorderedElement slot = case slotAnchor slot of+      Nothing -> False+      Just anchor -> any (`containsSpan` anchor) reorderable+    inputSet = Map.fromList ((,()) <$> inputSpans)+    counts = Map.fromListWith (+) ((,1 :: Int) <$> emitted)++    dropped =+      [CommentDropped spn | spn <- sort inputSpans, not (spn `Map.member` counts)]+    duplicated =+      [ CommentDuplicated spn n+      | (spn, n) <- Map.toAscList counts,+        n > 1+      ]+    invented =+      [ CommentInvented spn+      | spn <- Map.keys counts,+        not (spn `Map.member` inputSet)+      ]++    -- Only the first emission of each comment is considered, so that a+    -- comment reported as duplicated is not also reported as reordered.+    reordered = go [] (dedupe [] ordered)+      where+        dedupe _ [] = []+        dedupe seen (x : xs)+          | x `elem` seen = dedupe seen xs+          | otherwise = x : dedupe (x : seen) xs+        go _ [] = []+        go seen (x : xs) =+          [CommentReordered x y | y <- seen, x < y]+            <> go (x : seen) xs++-- | Render a violation as a single line.+renderInvariantViolation :: InvariantViolation -> Text+renderInvariantViolation = \case+  CommentDropped spn ->+    "dropped     " <> renderSpan spn+  CommentDuplicated spn n ->+    "duplicated  " <> renderSpan spn <> " (emitted " <> showT n <> " times)"+  CommentInvented spn ->+    "invented    " <> renderSpan spn+  CommentReordered spn before ->+    "reordered   " <> renderSpan spn <> " (emitted after " <> renderSpan before <> ")"++renderSpan :: RealSrcSpan -> Text+renderSpan spn =+  renderLoc (realSrcSpanStart spn) <> "-" <> renderLoc (realSrcSpanEnd spn)+  where+    renderLoc l = showT (srcLocLine l) <> ":" <> showT (srcLocCol l)++showT :: (Show a) => a -> Text+showT = T.pack . show
+ src/Ormolu/Comments/Tree.hs view
@@ -0,0 +1,74 @@+-- | The containment tree of AST element spans.+--+-- This is the structure "Ormolu.Comments.Anchor" reads to place a comment:+-- given a comment, which element encloses it most tightly, and which of+-- that element's children does it fall between. Arranging the spans by+-- containment is what makes those questions answerable from position alone,+-- without reference to the order in which the printer visits anything.+module Ormolu.Comments.Tree+  ( SpanTree (..),+    mkSpanForest,+    countNodes,+  )+where++import Data.List (sortOn)+import Data.Ord (Down (..))+import GHC.Types.SrcLoc++-- | An element span together with the element spans it encloses. Children+-- are in ascending order and do not overlap each other.+data SpanTree = SpanTree+  { stSpan :: RealSrcSpan,+    stChildren :: [SpanTree]+  }+  deriving (Eq, Show)++-- | Arrange spans into a forest by containment.+--+-- Duplicates are dropped: several AST nodes routinely share one span (a+-- wrapper and the thing it wraps, say), and for the purpose of owning a+-- comment they are one element. Spans that overlap another without being+-- contained in it are dropped too—the GHC AST does produce such spans+-- occasionally, and they cannot be placed in a tree.+--+-- Zero-width spans are kept. An empty bracketed construct—an export or+-- import list, @[]@, a record with no fields—contains no element at all, so+-- the printer enters a zero-width one at its opening bracket+-- ('Ormolu.Printer.Combinators.locatedEmpty') to give a comment written+-- between the brackets something to attach to.+mkSpanForest :: [RealSrcSpan] -> [SpanTree]+mkSpanForest = goForest . dedupe . sortOn nestingOrder+  where+    -- Outermost first, so that a span is always seen before the spans it+    -- contains.+    nestingOrder s = (realSrcSpanStart s, Down (realSrcSpanEnd s))++    dedupe (x : y : rest) | x == y = dedupe (y : rest)+    dedupe (x : rest) = x : dedupe rest+    dedupe [] = []++    goForest [] = []+    goForest (s : rest) =+      let (children, rest') = goChildren s rest+       in SpanTree s children : goForest rest'++    goChildren parent = go []+      where+        go acc [] = (reverse acc, [])+        go acc (s : rest)+          | parent `containsSpan` s =+              let (children, rest') = goChildren s rest+               in go (SpanTree s children : acc) rest'+          | realSrcSpanStart s < realSrcSpanEnd parent =+              -- Overlaps the parent without being contained in it; there is+              -- no correct place for it, so leave it out.+              go acc rest+          | otherwise = (reverse acc, s : rest)++-- | How many elements the forest holds. Used by the tests to check that+-- duplicate and overlapping spans are dropped.+countNodes :: [SpanTree] -> Int+countNodes = sum . fmap node+  where+    node t = 1 + countNodes (stChildren t)
src/Ormolu/Config.hs view
@@ -35,6 +35,7 @@     fillMissingPrinterOpts,     resolvePrinterOpts,     CommaStyle (..),+    RecordStyle (..),     FunctionArrowsStyle (..),     HaddockPrintStyle (..),     HaddockPrintStyleModule (..),@@ -43,12 +44,13 @@     ImportGrouping (..),     ImportGroup (..),     ImportGroupRule (..),-    ImportModuleMatcher (..),     ImportRulePriority (..),     matchAllRulePriority,     matchLocalRulePriority,     defaultImportRulePriority,+    ImportListMatcher (..),     QualifiedImportMatcher (..),+    ImportScopeMatcher (..),     LetStyle (..),     InStyle (..),     IfStyle (..),@@ -111,7 +113,8 @@     cfgDynOptions :: ![DynOption],     -- | Fixity overrides     cfgFixityOverrides :: !FixityOverrides,-    -- | Module reexports to take into account when doing fixity resolution+    -- | Module re-exports to take into account when performing fixity+    -- resolution     cfgModuleReexports :: !ModuleReexports,     -- | Known dependencies, if any     cfgDependencies :: !(Set PackageName),@@ -119,9 +122,9 @@     cfgUnsafe :: !Bool,     -- | Output information useful for debugging     cfgDebug :: !Bool,-    -- | Checks if re-formatting the result is idempotent+    -- | Check that re-formatting the result is idempotent     cfgCheckIdempotence :: !Bool,-    -- | How to parse the input (regular haskell module or Backpack file)+    -- | How to parse the input (a regular Haskell module or a Backpack file)     cfgSourceType :: !SourceType,     -- | Whether to use colors and other features of ANSI terminals     cfgColorMode :: !ColorMode,
src/Ormolu/Config/Gen.hs view
@@ -1,5 +1,5 @@ {- FOURMOLU_DISABLE -}-{- ***** DO NOT EDIT: This module is autogenerated ***** -}+{- DO NOT EDIT: This module is autogenerated -}  {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-}@@ -9,6 +9,7 @@ module Ormolu.Config.Gen   ( PrinterOpts (..)   , CommaStyle (..)+  , RecordStyle (..)   , FunctionArrowsStyle (..)   , HaddockPrintStyle (..)   , HaddockPrintStyleModule (..)@@ -54,6 +55,8 @@       poFunctionArrows :: f FunctionArrowsStyle     , -- | How to place commas in multi-line lists, records, etc.       poCommaStyle :: f CommaStyle+    , -- | How to place braces in records+      poRecordStyle :: f RecordStyle     , -- | Styling of import/export lists       poImportExportStyle :: f ImportExportStyle     , -- | Rules for grouping import declarations@@ -102,6 +105,7 @@     , poColumnLimit = Nothing     , poFunctionArrows = Nothing     , poCommaStyle = Nothing+    , poRecordStyle = Nothing     , poImportExportStyle = Nothing     , poImportGrouping = Nothing     , poIndentWheres = Nothing@@ -130,6 +134,7 @@     , poColumnLimit = pure NoLimit     , poFunctionArrows = pure TrailingArrows     , poCommaStyle = pure Leading+    , poRecordStyle = pure RecordStyleAligned     , poImportExportStyle = pure ImportExportDiffFriendly     , poImportGrouping = pure ImportGroupLegacy     , poIndentWheres = pure False@@ -165,6 +170,7 @@     , poColumnLimit = maybe (poColumnLimit p2) pure (poColumnLimit p1)     , poFunctionArrows = maybe (poFunctionArrows p2) pure (poFunctionArrows p1)     , poCommaStyle = maybe (poCommaStyle p2) pure (poCommaStyle p1)+    , poRecordStyle = maybe (poRecordStyle p2) pure (poRecordStyle p1)     , poImportExportStyle = maybe (poImportExportStyle p2) pure (poImportExportStyle p1)     , poImportGrouping = maybe (poImportGrouping p2) pure (poImportGrouping p1)     , poIndentWheres = maybe (poIndentWheres p2) pure (poIndentWheres p1)@@ -209,6 +215,10 @@       "How to place commas in multi-line lists, records, etc. (choices: \"leading\" or \"trailing\") (default: leading)"       "OPTION"     <*> f+      "record-style"+      "How to place braces in records (choices: \"aligned\" or \"knr\") (default: aligned)"+      "OPTION"+    <*> f       "import-export-style"       "Styling of import/export lists (choices: \"leading\", \"trailing\", or \"diff-friendly\") (default: diff-friendly)"       "OPTION"@@ -230,7 +240,7 @@       "INT"     <*> f       "haddock-style"-      "How to print Haddock comments (choices: \"single-line\", \"multi-line\", or \"multi-line-compact\") (default: multi-line)"+      "How to print Haddock comments (choices: \"single-line\", \"multi-line\", \"multi-line-compact\", or \"auto\") (default: multi-line)"       "OPTION"     <*> f       "haddock-style-module"@@ -295,6 +305,7 @@     <*> f "column-limit"     <*> f "function-arrows"     <*> f "comma-style"+    <*> f "record-style"     <*> f "import-export-style"     <*> f "import-grouping"     <*> f "indent-wheres"@@ -348,6 +359,11 @@   | Trailing   deriving (Eq, Show, Enum, Bounded) +data RecordStyle+  = RecordStyleAligned+  | RecordStyleKnr+  deriving (Eq, Show, Enum, Bounded)+ data FunctionArrowsStyle   = TrailingArrows   | LeadingArrows@@ -358,6 +374,7 @@   = HaddockSingleLine   | HaddockMultiLine   | HaddockMultiLineCompact+  | HaddockAuto   deriving (Eq, Show, Enum, Bounded)  data HaddockPrintStyleModule@@ -450,6 +467,28 @@     Leading -> "leading"     Trailing -> "trailing" +instance Aeson.FromJSON RecordStyle where+  parseJSON =+    Aeson.withText "RecordStyle" $ \s ->+      either Aeson.parseFail pure $+        parsePrinterOptType (Text.unpack s)++instance PrinterOptsFieldType RecordStyle where+  parsePrinterOptType s =+    case s of+      "aligned" -> Right RecordStyleAligned+      "knr" -> Right RecordStyleKnr+      _ ->+        Left . unlines $+          [ "unknown value: " <> show s+          , "Valid values are: \"aligned\" or \"knr\""+          ]++instance RenderPrinterOpt RecordStyle where+  renderPrinterOpt = \case+    RecordStyleAligned -> "aligned"+    RecordStyleKnr -> "knr"+ instance Aeson.FromJSON FunctionArrowsStyle where   parseJSON =     Aeson.withText "FunctionArrowsStyle" $ \s ->@@ -486,10 +525,11 @@       "single-line" -> Right HaddockSingleLine       "multi-line" -> Right HaddockMultiLine       "multi-line-compact" -> Right HaddockMultiLineCompact+      "auto" -> Right HaddockAuto       _ ->         Left . unlines $           [ "unknown value: " <> show s-          , "Valid values are: \"single-line\", \"multi-line\", or \"multi-line-compact\""+          , "Valid values are: \"single-line\", \"multi-line\", \"multi-line-compact\", or \"auto\""           ]  instance RenderPrinterOpt HaddockPrintStyle where@@ -497,6 +537,7 @@     HaddockSingleLine -> "single-line"     HaddockMultiLine -> "multi-line"     HaddockMultiLineCompact -> "multi-line-compact"+    HaddockAuto -> "auto"  instance Aeson.FromJSON HaddockPrintStyleModule where   parseJSON =@@ -777,6 +818,9 @@     , "# How to place commas in multi-line lists, records, etc. (choices: leading or trailing)"     , "comma-style: leading"     , ""+    , "# How to place braces in records (choices: aligned or knr)"+    , "record-style: aligned"+    , ""     , "# Styling of import/export lists (choices: leading, trailing, or diff-friendly)"     , "import-export-style: diff-friendly"     , ""@@ -792,7 +836,7 @@     , "# Number of spaces between top-level declarations"     , "newlines-between-decls: 1"     , ""-    , "# How to print Haddock comments (choices: single-line, multi-line, or multi-line-compact)"+    , "# How to print Haddock comments (choices: single-line, multi-line, multi-line-compact, or auto)"     , "haddock-style: multi-line"     , ""     , "# How to print module docstring"
src/Ormolu/Config/Types.hs view
@@ -1,20 +1,22 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}  module Ormolu.Config.Types   ( ImportGroup (..),     ImportGroupRule (..),-    ImportModuleMatcher (..),     ImportRulePriority (..),     matchAllRulePriority,     matchLocalRulePriority,     defaultImportRulePriority,+    ImportListMatcher (..),     QualifiedImportMatcher (..),+    ImportScopeMatcher (..),   ) where -import Control.Applicative (Alternative (..), asum)-import Data.Aeson ((.!=), (.:), (.:?))+import Control.Applicative (Alternative (..))+import Data.Aeson ((.!=), (.:?)) import Data.Aeson qualified as Aeson import Data.Aeson.Types qualified as Aeson import Data.List.NonEmpty (NonEmpty)@@ -34,28 +36,47 @@       <*> Aeson.parseField o "rules"  data ImportGroupRule = ImportGroupRule-  { igrModuleMatcher :: !ImportModuleMatcher,+  { igrGlob :: !Glob,+    igrImportListMatcher :: !ImportListMatcher,     igrQualifiedMatcher :: !QualifiedImportMatcher,+    igrScopeMatcher :: !ImportScopeMatcher,     igrPriority :: !ImportRulePriority   }   deriving (Eq, Show)  instance Aeson.FromJSON ImportGroupRule where   parseJSON = Aeson.withObject "rule" $ \o -> do-    let parseModuleMatcher = Aeson.parseJSON (Aeson.Object o)-        failUnknownModuleMatcher = Aeson.parseFail "Unknown or invalid module matcher"-        attemptParseModuleMatcher = parseModuleMatcher <|> failUnknownModuleMatcher-    igrModuleMatcher <- attemptParseModuleMatcher+    legacyMatch <- o .:? "match"+    (defaultPriority, defaultScope) <- case legacyMatch of+      Just "all" -> pure (matchAllRulePriority, pure MatchAllModules)+      Just "local-modules" -> pure (matchLocalRulePriority, pure MatchLocalModules)+      Just other -> Aeson.parseFail $ "Unknown legacy match value: " <> other+      Nothing -> pure (defaultImportRulePriority, empty) -    qualified <- o .:? "qualified"-    igrQualifiedMatcher <- case qualified of-      Just True -> pure MatchQualifiedOnly-      Just False -> pure MatchUnqualifiedOnly-      Nothing -> pure MatchBothQualifiedAndUnqualified+    igrGlob <- mkGlob <$> o .:? "glob" .!= "**" -    let defaultPriority-          | MatchAllModules <- igrModuleMatcher = matchAllRulePriority-          | otherwise = defaultImportRulePriority+    igrImportListMatcher <-+      o .:? "import-list" .!= "any" >>= \case+        "any" -> pure MatchAnyImportDeclaration+        "explicit" -> pure MatchExplicitImportList+        "hiding" -> pure MatchHidingImportClause+        "none" -> pure MatchWholeModuleImport+        other -> Aeson.parseFail $ "Unknown import list matcher: " <> other++    igrQualifiedMatcher <-+      o .:? "qualified" >>= \case+        Just True -> pure MatchQualifiedOnly+        Just False -> pure MatchUnqualifiedOnly+        Nothing -> pure MatchBothQualifiedAndUnqualified++    igrScopeMatcher <-+      o .:? "scope" >>= \case+        Just "any" -> pure MatchAllModules+        Just "local" -> pure MatchLocalModules+        Just "external" -> pure MatchExternalModules+        Nothing -> defaultScope <|> pure MatchAllModules+        Just other -> Aeson.parseFail ("Unknown scope matcher: " <> other)+     igrPriority <- o .:? "priority" .!= defaultPriority      pure ImportGroupRule {..}@@ -75,35 +96,21 @@ defaultImportRulePriority :: ImportRulePriority defaultImportRulePriority = ImportRulePriority 50 +data ImportListMatcher+  = MatchExplicitImportList+  | MatchHidingImportClause+  | MatchWholeModuleImport+  | MatchAnyImportDeclaration+  deriving (Eq, Show)+ data QualifiedImportMatcher   = MatchQualifiedOnly   | MatchUnqualifiedOnly   | MatchBothQualifiedAndUnqualified   deriving (Eq, Show) -data ImportModuleMatcher-  = MatchAllModules+data ImportScopeMatcher+  = MatchExternalModules   | MatchLocalModules-  | MatchGlob !Glob+  | MatchAllModules   deriving (Eq, Show)--instance Aeson.FromJSON ImportModuleMatcher where-  parseJSON v =-    asum-      [ parseMatchModuleMatcher v,-        parseGlobModuleMatcher v-      ]-    where-      parseMatchModuleMatcher :: Aeson.Value -> Aeson.Parser ImportModuleMatcher-      parseMatchModuleMatcher = Aeson.withObject "ImportModuleMatcher" $ \o -> do-        c <- Aeson.parseField @String o "match"-        case c of-          "all" -> pure MatchAllModules-          "local-modules" -> pure MatchLocalModules-          other -> Aeson.parseFail $ "Unknown matcher: " <> other--      parseGlobModuleMatcher :: Aeson.Value -> Aeson.Parser ImportModuleMatcher-      parseGlobModuleMatcher = Aeson.withObject "ImportModuleMatcher" $ \o -> do-        rawGlob <- o .: "glob"-        let glob = mkGlob rawGlob-        pure (MatchGlob glob)
src/Ormolu/Diff/ParseResult.hs view
@@ -2,6 +2,8 @@ {-# LANGUAGE DeepSubsumption #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} @@ -9,15 +11,17 @@ module Ormolu.Diff.ParseResult   ( ParseResultDiff (..),     diffParseResult,+    diffCommentStream,   ) where  import Data.ByteString (ByteString) import Data.Char (isSpace)+import Data.Choice (pattern Without) import Data.Foldable import Data.Function import Data.Generics-import Data.List (sortOn)+import Data.List (sort, sortOn) import Data.Text qualified as T import GHC.Data.FastString (FastString) import GHC.Hs@@ -25,6 +29,7 @@ import GHC.Types.SrcLoc import Ormolu.Config.Gen (ImportGrouping (ImportGroupSingle)) import Ormolu.Imports (normalizeImports)+import Ormolu.Imports.Grouping (GroupImportsOpts (..)) import Ormolu.Parser.CommentStream import Ormolu.Parser.Result import Ormolu.Utils@@ -46,7 +51,12 @@ instance Monoid ParseResultDiff where   mempty = Same --- | Return 'Diff' of two 'ParseResult's.+-- | Compare the parse result of the input against that of the output.+--+-- Two of Ormolu's three comment checks live here: 'diffCommentStream' for+-- the text of the comments, and the syntax tree comparison for the+-- Haddocks, which are part of the tree rather than of the comment stream.+-- The third, "Ormolu.Comments.Invariants", checks where the comments went. diffParseResult ::   ParseResult ->   ParseResult ->@@ -62,15 +72,50 @@     } =     diffCommentStream cstream0 cstream1       <> diffHsModule-        hs0 {hsmodImports = concat . normalizeImports False mempty ImportGroupSingle $ hsmodImports hs0}-        hs1 {hsmodImports = concat . normalizeImports False mempty ImportGroupSingle $ hsmodImports hs1}+        hs0 {hsmodImports = concat $ normalizeImports' hs0}+        hs1 {hsmodImports = concat $ normalizeImports' hs1}+    where+      -- The exact parameters here don't matter, it just needs to be consistent+      normalizeImports' hsmod =+        normalizeImports+          GroupImportsOpts+            { grouping = ImportGroupSingle,+              respectful = False,+              allComments = listify (const True) hsmod+            }+          mempty+          (Without #implicitPrelude)+          (hsmodImports hsmod) +-- | Check that formatting did not change the /text/ of any comment.+--+-- This is the half of comment checking that works on what the comments say+-- rather than on where they went. Ormolu edits comment text on purpose—it+-- escapes Haddock triggers, re-indents block comments and normalizes the+-- spacing after a trigger—and both sides of this comparison have been+-- through 'Ormolu.Parser.CommentStream.mkCommentStream', so the intended+-- edits cancel out and only unintended ones show up.+--+-- What it deliberately does /not/ check:+--+--   * __order__, because Ormolu sorts imports, import lists and pragmas,+--     and a comment attached to one of those travels with it;+--   * __which comment is which__, since the lines are compared as a+--     multiset; a failure cannot say more than that the two sides differ,+--     which is why 'Different' is returned with no spans;+--   * __comments outside the stream__ — the Stack header and the comments+--     that travel with pragmas are lifted out of it during parsing, so+--     duplicating one of those is invisible here.+--+-- All three are covered by "Ormolu.Comments.Invariants", which compares+-- spans instead of text. Neither check subsumes the other and both run by+-- default. diffCommentStream :: CommentStream -> CommentStream -> ParseResultDiff diffCommentStream (CommentStream cs) (CommentStream cs')   | commentLines cs == commentLines cs' = Same   | otherwise = Different []   where-    commentLines = concatMap (toList . unComment . unLoc)+    commentLines = sort . concatMap (toList . unComment . unLoc)  -- | Compare two modules for equality disregarding certain semantically -- irrelevant features like exact print annotations.@@ -106,11 +151,13 @@                   `extQ` considerEqual @EpLayout                   `extQ` considerEqual @AnnSig                   `extQ` considerEqual @HsRuleAnn-                  `extQ` considerEqual @EpLinearArrow+                  `extQ` considerEqual @EpLinear                   `extQ` considerEqual @AnnSynDecl                   `extQ` considerEqual @IsUnicodeSyntax                   -- FastString (for example for string literals)                   `extQ` considerEqualVia' ((==) @FastString)+                  -- ModuleName is a newtype of FastString+                  `extQ` considerEqualVia' ((==) @ModuleName)                   -- Haddock strings                   `extQ` hsDocStringEq                   -- Whether imports are pre- or post-qualified@@ -170,14 +217,37 @@     epAnnEq :: EpAnn a -> b -> ParseResultDiff     epAnnEq _ _ = Same +    importDeclQualifiedStyleEq :: forall a. (Data a) => ImportDeclQualifiedStyle -> a -> ParseResultDiff     importDeclQualifiedStyleEq = considerEqualVia' f       where         f QualifiedPre QualifiedPost = True         f QualifiedPost QualifiedPre = True         f x x' = x == x' +    -- Documentation is compared up to the normalizations Ormolu performs+    -- on it: the space it puts after a Haddock's trigger, the+    -- re-indentation it gives a @{- | … -}@ so that the comment lines up+    -- with the code it documents, and the collapsing of consecutive blank+    -- lines. All three change the doc string GHC parses back out, and all+    -- three are intended.     hsDocStringEq :: HsDocString -> GenericQ ParseResultDiff-    hsDocStringEq = considerEqualVia' ((==) `on` (map (T.dropWhile isSpace) . splitDocString True))+    hsDocStringEq =+      considerEqualVia' ((==) `on` (collapseBlanks . dedent . splitDocString))+      where+        -- The printer emits at most one blank line in a row, as it does for+        -- ordinary comments.+        collapseBlanks = \case+          (x : y : rest)+            | T.null x, T.null y -> collapseBlanks (y : rest)+          (x : rest) -> x : collapseBlanks rest+          [] -> []+        dedent = \case+          [] -> []+          (x : xs) ->+            let indentOf l = T.length (T.takeWhile (== ' ') l)+                indents = indentOf <$> filter (not . T.all isSpace) xs+                n = if null indents then 0 else minimum indents+             in x : fmap (T.drop n) xs      forLocated ::       (Data e0, Data e1) =>
src/Ormolu/Diff/Text.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QualifiedDo #-}@@ -24,9 +23,6 @@ import GHC.Types.SrcLoc import Ormolu.Terminal import Ormolu.Terminal.QualifiedDo qualified as Term-#if !MIN_VERSION_base(4,20,0)-import Data.List (foldl')-#endif  ---------------------------------------------------------------------------- -- Types@@ -260,7 +256,7 @@       hunkDiff = mapDiff (fmap third) xs   return Hunk {..} --- | Trim empty “both” lines from beginning and end of a 'DiffList''.+-- | Trim empty “both” lines from the beginning and end of a 'DiffList''. trimEmpty :: DiffList' -> DiffList' trimEmpty = go True id   where
src/Ormolu/Exception.hs view
@@ -19,6 +19,7 @@ import Data.Void (Void) import Distribution.Parsec.Error (PError, showPError) import GHC.Types.SrcLoc+import Ormolu.Comments.Invariants (InvariantViolation, renderInvariantViolation) import Ormolu.Diff.Text (TextDiff, printTextDiff) import Ormolu.Terminal import Ormolu.Terminal.QualifiedDo qualified as Term@@ -36,6 +37,9 @@     OrmoluASTDiffers TextDiff [RealSrcSpan]   | -- | Formatted source code is not idempotent     OrmoluNonIdempotentOutput TextDiff+  | -- | The comments that came out do not correspond to the comments that+    -- went in+    OrmoluCommentInvariantsViolated FilePath [InvariantViolation]   | -- | Some GHC options were not recognized     OrmoluUnrecognizedOpts (NonEmpty String)   | -- | Cabal file parsing failed@@ -91,6 +95,22 @@     newline     put "  Please, consider reporting the bug."     newline+  OrmoluCommentInvariantsViolated path violations -> Term.do+    put (T.pack path)+    newline+    for_ violations $ \violation -> Term.do+      put "  "+      put (renderInvariantViolation violation)+      newline+    newline+    put "  The comments of the output do not correspond to the comments of"+    newline+    put "  the input."+    newline+    put "  Please, consider reporting the bug."+    newline+    put "  To format anyway, use --unsafe."+    newline   OrmoluUnrecognizedOpts opts -> Term.do     put "The following GHC options were not recognized:"     newline@@ -106,13 +126,13 @@   OrmoluMissingStdinInputFile -> Term.do     put "The --stdin-input-file option is necessary when using input"     newline-    put "from stdin and accounting for .cabal files"+    put "from stdin and accounting for .cabal files."     newline   OrmoluFixityOverridesParseError errorBundle -> Term.do     put . T.pack . errorBundlePretty $ errorBundle     newline --- | Inside this wrapper 'OrmoluException' will be caught and displayed+-- | Inside this wrapper, 'OrmoluException' will be caught and displayed -- nicely. withPrettyOrmoluExceptions ::   -- | Color mode@@ -126,12 +146,13 @@       runTerm (printOrmoluException e) colorMode stderr       return . ExitFailure $         case e of-          -- Error code 1 is for 'error' or 'notImplemented'-          -- 2 used to be for erroring out on CPP+          -- Error code 1 is for 'error' or 'notImplemented'.+          -- 2 used to be for erroring out on CPP.           OrmoluParsingFailed {} -> 3           OrmoluOutputParsingFailed {} -> 4           OrmoluASTDiffers {} -> 5           OrmoluNonIdempotentOutput {} -> 6+          OrmoluCommentInvariantsViolated {} -> 11           OrmoluUnrecognizedOpts {} -> 7           OrmoluCabalFileParsingFailed {} -> 8           OrmoluMissingStdinInputFile {} -> 9
src/Ormolu/Fixity.hs view
@@ -53,7 +53,7 @@   Binary.runGet Binary.get $     BL.fromStrict $(embedFile "extract-hackage-info/hackage-info.bin") --- | Default set of packages to assume as dependencies e.g. when no Cabal+-- | Default set of packages to assume as dependencies, e.g. when no Cabal -- file is found or taken into consideration. defaultDependencies :: Set PackageName defaultDependencies = Set.singleton (mkPackageName "base")
src/Ormolu/Fixity/Imports.hs view
@@ -75,7 +75,7 @@   IEThingWith _ (L _ x) _ xs _ -> occName x : fmap (occName . unLoc) xs   _ -> [] --- | Apply given module re-exports.+-- | Apply the given module re-exports. applyModuleReexports :: ModuleReexports -> [FixityImport] -> [FixityImport] applyModuleReexports (ModuleReexports reexports) imports = imports >>= expand   where
src/Ormolu/Fixity/Internal.hs view
@@ -74,7 +74,7 @@  {-# COMPLETE OpName #-} --- | Convert an 'OccName to an 'OpName'.+-- | Convert an 'OccName' to an 'OpName'. occOpName :: OccName -> OpName occOpName = MkOpName . fs_sbs . occNameFS @@ -125,10 +125,10 @@ data FixityApproximation = FixityApproximation   { -- | Fixity direction if it is known     faDirection :: Maybe FixityDirection,-    -- | Minimum precedence level found in the (maybe conflicting)+    -- | Minimum precedence level found in the (possibly conflicting)     -- definitions for the operator (inclusive)     faMinPrecedence :: Double,-    -- | Maximum precedence level found in the (maybe conflicting)+    -- | Maximum precedence level found in the (possibly conflicting)     -- definitions for the operator (inclusive)     faMaxPrecedence :: Double   }@@ -146,8 +146,8 @@     faMaxPrecedence <- Binary.getDoublele     pure FixityApproximation {..} --- | Gives the ability to merge two (maybe conflicting) definitions for an--- operator, keeping the higher level of compatible information from both.+-- | Gives the ability to merge two (possibly conflicting) definitions for+-- an operator, keeping the higher level of compatible information from both. instance Semigroup FixityApproximation where   FixityApproximation {faDirection = dir1, faMinPrecedence = min1, faMaxPrecedence = max1}     <> FixityApproximation {faDirection = dir2, faMinPrecedence = min2, faMaxPrecedence = max2} =@@ -190,7 +190,7 @@ defaultFixityOverrides :: FixityOverrides defaultFixityOverrides = FixityOverrides Map.empty --- | Module re-exports+-- | Module re-exports. newtype ModuleReexports = ModuleReexports   { unModuleReexports :: Map ModuleName (NonEmpty (Maybe PackageName, ModuleName))   }@@ -198,61 +198,7 @@  -- | Module re-exports to apply by default. defaultModuleReexports :: ModuleReexports-defaultModuleReexports =-  ModuleReexports . Map.fromList $-    [ ( "Control.Lens",-        l-          "lens"-          [ "Control.Lens.At",-            "Control.Lens.Cons",-            "Control.Lens.Each",-            "Control.Lens.Empty",-            "Control.Lens.Equality",-            "Control.Lens.Fold",-            "Control.Lens.Getter",-            "Control.Lens.Indexed",-            "Control.Lens.Iso",-            "Control.Lens.Lens",-            "Control.Lens.Level",-            "Control.Lens.Plated",-            "Control.Lens.Prism",-            "Control.Lens.Reified",-            "Control.Lens.Review",-            "Control.Lens.Setter",-            "Control.Lens.TH",-            "Control.Lens.Traversal",-            "Control.Lens.Tuple",-            "Control.Lens.Type",-            "Control.Lens.Wrapped",-            "Control.Lens.Zoom"-          ]-      ),-      ( "Servant",-        l-          "servant"-          [ "Servant.API"-          ]-      ),-      ( "Optics",-        l-          "optics"-          [ "Optics.Fold",-            "Optics.Operators",-            "Optics.IxAffineFold",-            "Optics.IxFold",-            "Optics.IxTraversal",-            "Optics.Traversal"-          ]-      ),-      ( "Test.Hspec",-        l-          "hspec-expectations"-          [ "Test.Hspec.Expectations"-          ]-      )-    ]-  where-    l packageName xs = (Just packageName,) <$> NE.fromList xs+defaultModuleReexports = ModuleReexports Map.empty  -- | Fixity information that is specific to a package being formatted. It -- requires module-specific imports in order to be usable.
src/Ormolu/Imports.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-}@@ -13,6 +13,8 @@  import Data.Bifunctor import Data.Char (isAlphaNum)+import Data.Choice (Choice)+import Data.Choice qualified as Choice import Data.Function (on) import Data.List (nubBy, sortBy, sortOn) import Data.Map.Strict (Map)@@ -27,31 +29,46 @@ import GHC.Types.PkgQual import GHC.Types.SourceText import GHC.Types.SrcLoc-import Ormolu.Config (ImportGrouping)-import Ormolu.Imports.Grouping (Import (..), groupImports, prepareExistingGroups)+import Ormolu.Imports.Grouping+  ( GroupImportsOpts,+    Import (..),+    ImportList (..),+    groupImports,+    prepareExistingGroups,+  ) import Ormolu.Utils (notImplemented, showOutputable)-#if !MIN_VERSION_base(4,20,0)-import Data.List (foldl')-#endif  -- | Sort, group and normalize imports. -- -- Assumes input list is sorted by source location. Output list is not necessarily -- sorted by source location, so this function should be called at most once on a -- given input list.-normalizeImports :: Bool -> Set Cabal.ModuleName -> ImportGrouping -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]-normalizeImports respectful localModules importGrouping =+normalizeImports ::+  GroupImportsOpts ->+  Set Cabal.ModuleName ->+  Choice "implicitPrelude" ->+  [LImportDecl GhcPs] ->+  [[LImportDecl GhcPs]]+normalizeImports groupImportsOpts localModules implicitPrelude =   map (fmap snd)     . concatMap-      ( groupImports importGrouping localModules toImport+      ( groupImports groupImportsOpts localModules toImport           . M.toAscList           . M.fromListWith combineImports-          . fmap (\x -> (importId x, g x))+          . fmap (\x -> (importId implicitPrelude x, g x))       )-    . prepareExistingGroups importGrouping respectful+    . prepareExistingGroups groupImportsOpts   where     toImport :: (ImportId, x) -> Import-    toImport (ImportId {..}, _) = Import {importName = importIdName, importQualified}+    toImport (ImportId {..}, _) =+      Import+        { importName = importIdName,+          importList = case importHiding of+            Just (ImportListInterpretationOrd Exactly) -> Just ImportList+            Just (ImportListInterpretationOrd EverythingBut) -> Just HidingList+            Nothing -> Nothing,+          importQualified+        }      g :: LImportDecl GhcPs -> LImportDecl GhcPs     g (L l ImportDecl {..}) =@@ -68,20 +85,41 @@   LImportDecl GhcPs ->   LImportDecl GhcPs ->   LImportDecl GhcPs-combineImports (L lx ImportDecl {..}) (L _ y) =-  L-    lx-    ImportDecl-      { ideclImportList = case (ideclImportList, GHC.ideclImportList y) of-          (Just (hiding, L l' xs), Just (_, L _ ys)) ->-            Just (hiding, (L l' (normalizeLies (xs ++ ys))))-          _ -> Nothing,-        ..-      }+combineImports x y =+  L widenedLoc earlier {ideclImportList = combinedImportList}+  where+    -- The merged declaration spans both of the ones it came from, so that+    -- it is laid out over several lines and a comment that was written+    -- between them has somewhere to go. Its own span would otherwise still+    -- describe a single line.+    widenedLoc =+      l {entry = EpaSpan (combineSrcSpans (locA (getLoc x)) (locA (getLoc y)))} --- | Import id, a collection of all things that justify having a separate--- import entry. This is used for merging of imports. If two imports have--- the same 'ImportId' they can be merged.+    -- Take the declaration that comes first in the source whole, rather+    -- than mixing the span of one with the contents of the other: a+    -- declaration whose span sits before its own module name or import+    -- list cannot be placed in a containment tree, which is what comment+    -- attachment needs.+    (L l earlier, L _ later)+      | startsFirst (locA (getLoc x)) (locA (getLoc y)) = (x, y)+      | otherwise = (y, x)+    combinedImportList =+      case (GHC.ideclImportList earlier, GHC.ideclImportList later) of+        (Just (hiding, L l' xs), Just (_, L _ ys)) ->+          Just (hiding, L l' (normalizeLies (xs ++ ys)))+        _ -> Nothing++-- | Does the first span start before the second? Spans without a real+-- location are treated as coming first, arbitrarily but consistently.+startsFirst :: SrcSpan -> SrcSpan -> Bool+startsFirst a b = case (srcSpanToRealSrcSpan a, srcSpanToRealSrcSpan b) of+  (Just a', Just b') -> realSrcSpanStart a' <= realSrcSpanStart b'+  (Nothing, _) -> True+  _ -> False++-- | An import id, a collection of all the things that justify having a+-- separate import entry. This is used for merging imports: if two imports+-- have the same 'ImportId', they can be merged. data ImportId = ImportId   { importIsPrelude :: Bool,     importPkgQual :: ImportPkgQual,@@ -90,10 +128,23 @@     importSafe :: Bool,     importQualified :: Bool,     importAs :: Maybe ModuleName,-    importHiding :: Maybe ImportListInterpretationOrd+    importHiding :: Maybe ImportListInterpretationOrd,+    importLevel :: Maybe ImportDeclLevelOrd   }   deriving (Eq, Ord) +-- | A wrapper for 'ImportDeclLevel' that provides an 'Ord' instance.+newtype ImportDeclLevelOrd = ImportDeclLevelOrd+  { unImportDeclLevelOrd :: ImportDeclLevel+  }+  deriving stock (Eq)++instance Ord ImportDeclLevelOrd where+  compare = compare `on` toBool . unImportDeclLevelOrd+    where+      toBool ImportDeclSplice = False+      toBool ImportDeclQuote = True+ data ImportPkgQual   = -- | The import is not qualified by a package name.     NoImportPkgQual@@ -124,8 +175,8 @@       toBool EverythingBut = True  -- | Obtain an 'ImportId' for a given import.-importId :: LImportDecl GhcPs -> ImportId-importId (L _ ImportDecl {..}) =+importId :: Choice "implicitPrelude" -> LImportDecl GhcPs -> ImportId+importId implicitPrelude (L _ ImportDecl {..}) =   ImportId     { importIsPrelude = isPrelude,       importIdName = moduleName,@@ -137,11 +188,17 @@         QualifiedPost -> True         NotQualified -> False,       importAs = unLoc <$> ideclAs,-      importHiding = ImportListInterpretationOrd . fst <$> ideclImportList+      importHiding = ImportListInterpretationOrd . fst <$> ideclImportList,+      importLevel = importLevelOf ideclLevelSpec     }   where-    isPrelude = moduleNameString moduleName == "Prelude"+    isPrelude =+      Choice.isTrue implicitPrelude && moduleNameString moduleName == "Prelude"     moduleName = unLoc ideclName+    importLevelOf = \case+      LevelStylePre l -> Just (ImportDeclLevelOrd l)+      LevelStylePost l -> Just (ImportDeclLevelOrd l)+      NotLevelled -> Nothing  -- | Normalize a collection of import items. normalizeLies :: [LIE GhcPs] -> [LIE GhcPs]@@ -221,7 +278,7 @@ compareLIewn :: LIEWrappedName GhcPs -> LIEWrappedName GhcPs -> Ordering compareLIewn = compareIewn `on` unLoc --- | Compare two @'IEWrapppedName' 'GhcPs'@ things.+-- | Compare two @'IEWrappedName' 'GhcPs'@ things. compareIewn :: IEWrappedName GhcPs -> IEWrappedName GhcPs -> Ordering compareIewn = (comparing fst <> (compareRdrName `on` unLoc . snd)) `on` classify   where@@ -231,6 +288,7 @@       IEDefault _ x -> (1, x)       IEPattern _ x -> (2, x)       IEType _ x -> (3, x)+      IEData _ x -> (4, x)  compareRdrName :: RdrName -> RdrName -> Ordering compareRdrName x y =
src/Ormolu/Imports/Grouping.hs view
@@ -1,8 +1,12 @@ {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NoFieldSelectors #-}  module Ormolu.Imports.Grouping   ( Import (..),+    ImportList (..),+    GroupImportsOpts (..),     prepareExistingGroups,     groupImports,   )@@ -14,23 +18,32 @@ import Data.List (groupBy, minimumBy, sortOn) import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NonEmpty+import Data.Map qualified as Map+import Data.Maybe (fromMaybe) import Data.Set (Set) import Data.Set qualified as Set import Distribution.ModuleName qualified as Cabal-import GHC.Hs (GhcPs, getLocA)+import GHC.Hs (GhcPs, LEpaComment, epaLocationRealSrcSpan, getLocA)+import GHC.Types.SrcLoc (getLoc, srcSpanEndLine, srcSpanStartLine, srcSpanToRealSrcSpan) import Language.Haskell.Syntax (LImportDecl, ModuleName, moduleNameString)-import Ormolu.Config (ImportGroup (..), ImportGroupRule (..), ImportGrouping (..), ImportModuleMatcher (..))+import Ormolu.Config (ImportGroup (..), ImportGroupRule (..), ImportGrouping (..)) import Ormolu.Config qualified as Config-import Ormolu.Utils (ghcModuleNameToCabal, groupBy', separatedByBlank)-import Ormolu.Utils.Glob (matchesGlob)+import Ormolu.Utils (ghcModuleNameToCabal, groupBy')+import Ormolu.Utils.Glob (matchAllGlob, matchesGlob)  newtype ImportGroups = ImportGroups (NonEmpty ImportGroup)  data Import = Import   { importName :: ModuleName,+    importList :: Maybe ImportList,     importQualified :: Bool   } +data ImportList+  = ImportList+  | HidingList+  deriving (Eq)+ importGroupSingleStrategy :: ImportGroups importGroupSingleStrategy =   ImportGroups $@@ -94,16 +107,20 @@ matchAllImportRule :: ImportGroupRule matchAllImportRule =   ImportGroupRule-    { igrModuleMatcher = MatchAllModules,+    { igrGlob = matchAllGlob,+      igrImportListMatcher = Config.MatchAnyImportDeclaration,       igrQualifiedMatcher = Config.MatchBothQualifiedAndUnqualified,+      igrScopeMatcher = Config.MatchAllModules,       igrPriority = Config.matchAllRulePriority     }  matchLocalModulesRule :: ImportGroupRule matchLocalModulesRule =   ImportGroupRule-    { igrModuleMatcher = MatchLocalModules,+    { igrGlob = matchAllGlob,+      igrImportListMatcher = Config.MatchAnyImportDeclaration,       igrQualifiedMatcher = Config.MatchBothQualifiedAndUnqualified,+      igrScopeMatcher = Config.MatchLocalModules,       igrPriority = Config.matchLocalRulePriority     } @@ -122,31 +139,78 @@     }  matchesRule :: Set Cabal.ModuleName -> Import -> ImportGroupRule -> Bool-matchesRule localMods Import {..} ImportGroupRule {..} = matchesModules && matchesQualified+matchesRule localMods Import {..} ImportGroupRule {..} =+  and+    [ matchingGlob,+      matchingImportList,+      matchingQualified,+      matchingScope+    ]   where-    matchesModules = case igrModuleMatcher of-      MatchAllModules -> True-      MatchLocalModules -> ghcModuleNameToCabal importName `Set.member` localMods-      MatchGlob gl -> moduleNameString importName `matchesGlob` gl-    matchesQualified = case igrQualifiedMatcher of+    matchingGlob = moduleNameString importName `matchesGlob` igrGlob+    matchingImportList = case igrImportListMatcher of+      Config.MatchExplicitImportList -> importList == Just ImportList+      Config.MatchHidingImportClause -> importList == Just HidingList+      Config.MatchWholeModuleImport -> importList == Nothing+      Config.MatchAnyImportDeclaration -> True+    matchingQualified = case igrQualifiedMatcher of       Config.MatchQualifiedOnly -> importQualified       Config.MatchUnqualifiedOnly -> not importQualified       Config.MatchBothQualifiedAndUnqualified -> True+    matchingScope =+      let isLocalModule = ghcModuleNameToCabal importName `Set.member` localMods+       in case igrScopeMatcher of+            Config.MatchAllModules -> True+            Config.MatchExternalModules -> not isLocalModule+            Config.MatchLocalModules -> isLocalModule -prepareExistingGroups :: ImportGrouping -> Bool -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]-prepareExistingGroups ig respectful =-  case ig of+data GroupImportsOpts = GroupImportsOpts+  { grouping :: ImportGrouping,+    respectful :: Bool,+    -- | All comments in the HsModule.+    --+    -- Can't retrieve comments from 'R', since 'R' runs the first time without+    -- comments.+    allComments :: [LEpaComment]+  }++prepareExistingGroups :: GroupImportsOpts -> [LImportDecl GhcPs] -> [[LImportDecl GhcPs]]+prepareExistingGroups opts =+  case opts.grouping of     ImportGroupPreserve -> preserveGroups-    ImportGroupLegacy | respectful -> preserveGroups+    ImportGroupLegacy | opts.respectful -> preserveGroups     _ -> flattenGroups   where-    preserveGroups = map toList . groupBy' (\x y -> not $ separatedByBlank getLocA x y)+    preserveGroups = map toList . groupBy' (\x y -> not $ separatedByBlank' x y)     flattenGroups = pure -groupImports :: forall x. ImportGrouping -> Set Cabal.ModuleName -> (x -> Import) -> [x] -> [[x]]-groupImports ig localModules fToImport = regroup . fmap (breakTies . matchRules)+    -- separatedByBlank only checks if the span lines are more than 1 apart.+    -- If there's a comment between two imports with no blank lines, we should+    -- still consider it one import group.+    separatedByBlank' a b =+      fromMaybe False $ do+        endA <- srcSpanEndLine <$> srcSpanToRealSrcSpan (getLocA a)+        startB <- srcSpanStartLine <$> srcSpanToRealSrcSpan (getLocA b)+        pure . any (not . hasComment) $ [endA + 1 .. startB - 1]++    -- Maps startLine -> endLine+    commentLineIntervals =+      Map.fromList+        [ (srcSpanStartLine spn, srcSpanEndLine spn)+        | comment <- opts.allComments,+          let spn = epaLocationRealSrcSpan $ getLoc comment+        ]+    hasComment lineNum =+      (not . Map.null)+        -- Find any comment where: startLine <= lineNum <= endLine+        . Map.filter (>= lineNum)+        . Map.takeWhileAntitone (<= lineNum)+        $ commentLineIntervals++groupImports :: forall x. GroupImportsOpts -> Set Cabal.ModuleName -> (x -> Import) -> [x] -> [[x]]+groupImports opts localModules fToImport = regroup . fmap (breakTies . matchRules)   where-    ImportGroups igs = groupsFromConfig ig+    ImportGroups igs = groupsFromConfig opts.grouping      indexedGroupRules :: [(Int, [ImportGroupRule])]     indexedGroupRules = zip [0 ..] (toList . igRules <$> toList igs)
src/Ormolu/Parser.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}@@ -18,6 +19,8 @@ import Control.Monad.Except (ExceptT (..), runExceptT) import Control.Monad.IO.Class import Data.Char (isSpace)+import Data.Choice (Choice)+import Data.Choice qualified as Choice import Data.Functor import Data.Functor.Identity (Identity (..)) import Data.Generics hiding (orElse)@@ -30,7 +33,7 @@ import GHC.Data.FastString qualified as GHC import GHC.Data.Maybe (orElse) import GHC.Data.StringBuffer (StringBuffer)-import GHC.Driver.Config.Parser (initParserOpts)+import GHC.Driver.Config.Parser (initParserOpts, supportedLanguagePragmas) import GHC.Driver.Errors.Types qualified as GHC import GHC.Driver.Session as GHC import GHC.DynFlags (baseDynFlags)@@ -45,6 +48,7 @@ import GHC.Types.SrcLoc import GHC.Utils.Error import GHC.Utils.Exception (ExceptionMonad)+import GHC.Utils.Logger (initLogger) import GHC.Utils.Panic qualified as GHC import Ormolu.Config import Ormolu.Config.Gen (SingleConstraintParens (..))@@ -91,8 +95,12 @@     parsePragmasIntoDynFlags baseFlags extraOpts path rawInputStringBuffer >>= \case       Right res -> pure res       Left err -> throwIO (OrmoluParsingFailed beginningLoc err)-  let cppEnabled = EnumSet.member Cpp (GHC.extensionFlags dynFlags)-      implicitPrelude = EnumSet.member ImplicitPrelude (GHC.extensionFlags dynFlags)+  let cppEnabled =+        Choice.fromBool $+          EnumSet.member Cpp (GHC.extensionFlags dynFlags)+      implicitPrelude =+        Choice.fromBool $+          EnumSet.member ImplicitPrelude (GHC.extensionFlags dynFlags)   fixityImports <-     parseImports dynFlags implicitPrelude path rawInputStringBuffer >>= \case       Right res ->@@ -144,20 +152,23 @@       parser = case cfgSourceType of         ModuleSource -> GHC.parseModule         SignatureSource -> GHC.parseSignature+      implicitPrelude =+        Choice.fromBool $+          EnumSet.member ImplicitPrelude (GHC.extensionFlags dynFlags)       r = case runParser parser dynFlags path input of         GHC.PFailed pstate ->           case pStateErrors pstate of             Just err -> Left err             Nothing -> error "PFailed does not have an error"-        GHC.POk pstate (L _ (normalizeModule config -> hsModule)) ->+        GHC.POk pstate (L _ (normalizeModule implicitPrelude config -> hsModule)) ->           case pStateErrors pstate of-            -- Some parse errors (pattern/arrow syntax in expr context)-            -- do not cause a parse error, but they are replaced with "_"-            -- by the parser and the modified AST is propagated to the-            -- later stages; but we fail in those cases.+            -- Some malformed inputs (pattern/arrow syntax in an+            -- expression context) do not cause a parse error; instead the+            -- parser replaces them with "_" and propagates the modified AST+            -- to the later stages. We fail in those cases.             Just err -> Left err             Nothing ->-              let (stackHeader, pragmas, comments) =+              let (stackHeader, pragmas, comments, haddockText) =                     mkCommentStream input hsModule                in Right                     ParseResult@@ -166,6 +177,7 @@                         prStackHeader = stackHeader,                         prPragmas = pragmas,                         prCommentStream = comments,+                        prHaddockText = haddockText,                         prExtensions = GHC.extensionFlags dynFlags,                         prModuleFixityMap = modFixityMap,                         prIndent = indent,@@ -175,11 +187,16 @@  -- | Normalize a 'HsModule' by sorting its export lists, dropping -- blank comments, etc.-normalizeModule :: Config RegionDeltas -> HsModule GhcPs -> HsModule GhcPs-normalizeModule Config {..} hsmod =+normalizeModule ::+  Choice "implicitPrelude" ->+  Config RegionDeltas ->+  HsModule GhcPs ->+  HsModule GhcPs+normalizeModule _ Config {..} hsmod =   everywhere     ( mkT dropBlankTypeHaddocks         `extT` dropBlankDataDeclHaddocks+        `extT` dropBlankConDeclFieldHaddocks         `extT` patchContext         `extT` patchExprContext     )@@ -209,6 +226,13 @@       L _ (HsDocTy _ ty s) :: LHsType GhcPs         | isBlankDocString s -> ty       a -> a+    -- A Haddock on a field that holds nothing but whitespace is dropped,+    -- the same way one on a constructor is. Without this, whether it+    -- survives depends on whether it happened to end in a space.+    dropBlankConDeclFieldHaddocks = \case+      CDF {cdf_doc = Just s, ..} :: HsConDeclField GhcPs+        | isBlankDocString s -> CDF {cdf_doc = Nothing, ..}+      a -> a     dropBlankDataDeclHaddocks = \case       ConDeclGADT {con_doc = Just s, ..} :: ConDecl GhcPs         | isBlankDocString s -> ConDeclGADT {con_doc = Nothing, ..}@@ -217,7 +241,7 @@       a -> a      -- For constraint contexts (both in types and in expressions), normalize-    -- parenthesis as decided in https://github.com/tweag/ormolu/issues/264.+    -- parentheses as decided in https://github.com/tweag/ormolu/issues/264.     patchContext :: LHsContext GhcPs -> LHsContext GhcPs     patchContext = fmap $ \case       [x@(L _ (HsParTy _ inner))]@@ -246,7 +270,7 @@     allExts = [minBound .. maxBound]  -- | Extensions that are not enabled automatically and should be activated--- by user.+-- by the user. manualExts :: [Extension] manualExts =   [ Arrows, -- steals proc@@ -264,11 +288,11 @@     UnboxedSums,     UnicodeSyntax, -- gives special meanings to operators like (→)     TemplateHaskell, -- changes how $foo is parsed-    TemplateHaskellQuotes, -- enables TH subset of quasi-quotes, this+    TemplateHaskellQuotes, -- enables the TH subset of quasi-quotes, which     -- apparently interferes with QuasiQuotes in     -- weird ways     ImportQualifiedPost, -- affects how Ormolu renders imports, so the-    -- decision of enabling this style is left to the user+    -- decision to enable this style is left to the user     NegativeLiterals, -- with this, `- 1` and `-1` have differing AST     LexicalNegation, -- implies NegativeLiterals     LinearTypes, -- steals the (%) type operator in some cases@@ -317,10 +341,14 @@     let (_warnings, fileOpts) =           GHC.getOptions             (initParserOpts flags)+            (supportedLanguagePragmas flags)             input             filepath+    -- 'initLogger' does not have any hooks installed, so we don't get any+    -- (unwanted) output.+    logger <- initLogger     (flags', leftovers, warnings) <--      parseDynamicFilePragma flags (extraOpts <> fileOpts)+      parseDynamicFilePragma logger flags (extraOpts <> fileOpts)     case NE.nonEmpty leftovers of       Nothing -> return ()       Just unrecognizedOpts ->@@ -332,8 +360,8 @@ parseImports ::   -- | Pre-set 'DynFlags'   DynFlags ->-  -- | Implicit Prelude?-  Bool ->+  -- | Whether the implicit Prelude is in effect+  Choice "implicitPrelude" ->   -- | File name (only for source location annotations)   FilePath ->   -- | Input for the parser@@ -355,7 +383,11 @@                     mod' = mmoduleName `orElse` L (GHC.noAnnSrcSpan main_loc) mAIN_NAME                     explicitImports = hsmodImports hsmod                     implicitImports =-                      GHC.mkPrelImports (unLoc mod') main_loc implicitPrelude explicitImports+                      GHC.mkPrelImports+                        (unLoc mod')+                        main_loc+                        (Choice.toBool implicitPrelude)+                        explicitImports                  in Right (explicitImports ++ implicitImports)   where     popts = initParserOpts flags
src/Ormolu/Parser/CommentStream.hs view
@@ -2,11 +2,12 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ViewPatterns #-} --- | Functions for working with comment stream.+-- | Functions for working with the comment stream. module Ormolu.Parser.CommentStream   ( -- * Comment stream     CommentStream (..),     mkCommentStream,+    HaddockText,      -- * Comment     LComment,@@ -17,10 +18,10 @@   ) where -import Control.Monad ((<=<)) import Data.Char (isSpace) import Data.Data (Data) import Data.Generics.Schemes+import Data.List qualified as L import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE import Data.Map.Lazy qualified as M@@ -29,7 +30,7 @@ import Data.Text (Text) import Data.Text qualified as T import GHC.Data.Strict qualified as Strict-import GHC.Hs (HsModule)+import GHC.Hs (HsModule (..)) import GHC.Hs.Doc import GHC.Hs.Extension import GHC.Hs.ImpExp@@ -43,29 +44,63 @@ -- Comment stream  -- | A stream of 'RealLocated' 'Comment's in ascending order with respect to--- beginning of corresponding spans.+-- the beginning of the corresponding spans. newtype CommentStream = CommentStream [LComment]   deriving (Eq, Data, Semigroup, Monoid) --- | Create 'CommentStream' from 'HsModule'. The pragmas are--- removed from the 'CommentStream'.+-- | The source text of the Haddocks of a module, keyed by span.+--+-- Haddocks are printed from the text the author wrote rather than+-- reconstructed from the 'GHC.Hs.Doc.HsDocString' GHC parsed out of it.+-- Reconstruction cannot preserve everything — an empty @-- |@ vanishes, and+-- a @{- | … -}@ cannot come back as anything but @--@ lines — and what it+-- loses, it loses from the AST as well, which is why Ormolu refuses to+-- format some modules it should be able to.+type HaddockText = M.Map RealSrcSpan Comment++-- | Create a 'CommentStream' from an 'HsModule'. The pragmas are removed+-- from the 'CommentStream'. mkCommentStream ::   -- | Original input   Text ->   -- | Module to use for comment extraction   HsModule GhcPs ->-  -- | Stack header, pragmas, and comment stream+  -- | Stack header, pragmas, comment stream, and Haddock source text   ( Maybe LComment,     [([LComment], Pragma)],-    CommentStream+    CommentStream,+    HaddockText   ) mkCommentStream input hsModule =   ( mstackHeader,     pragmas,-    CommentStream comments+    CommentStream comments,+    haddockText   )   where-    (comments, pragmas) = extractPragmas input rawComments1+    -- The Haddocks are kept out of the comment stream, because they are+    -- printed from the AST, but their text is kept so that the printer can+    -- reproduce what the author wrote.+    haddockText =+      M.fromList+        [ (spn, mkHaddockComment (L spn (sliceSpan input spn)))+        | spn <- S.toList validHaddockCommentSpans+        ]++    (comments, pragmas) = extractPragmas input headerEnd rawComments1++    -- Where the file header stops and the module proper begins. Only+    -- pragmas before this point are hoisted and normalized; GHC reads the+    -- header and nothing else, so a pragma below it has no effect on+    -- compilation and moving it to the top would give it one.+    headerEnd =+      listToMaybe . L.sort $+        [ realSrcSpanStart spn+        | l <-+            (getLocA <$> hsmodImports hsModule)+              <> (getLocA <$> hsmodDecls hsModule),+          Just spn <- [srcSpanToRealSrcSpan l]+        ]     (rawComments1, mstackHeader) = extractStackHeader rawComments0      -- We want to extract all comments except _valid_ Haddock comments@@ -75,32 +110,33 @@         . flip M.withoutKeys validHaddockCommentSpans         . M.fromList         . fmap (\(L l a) -> (l, a))-        $ allComments+        $ allRawComments++    -- All comments, including valid and invalid Haddock comments+    allRawComments =+      mapMaybe (unAnnotationComment input) $+        epAnnCommentsToList =<< listify (only @EpAnnComments) hsModule       where-        -- All comments, including valid and invalid Haddock comments-        allComments =-          mapMaybe unAnnotationComment $-            epAnnCommentsToList =<< listify (only @EpAnnComments) hsModule-          where-            epAnnCommentsToList = \case-              EpaComments cs -> cs-              EpaCommentsBalanced pcs fcs -> pcs <> fcs-        -- All spans of valid Haddock comments-        validHaddockCommentSpans =-          S.fromList-            . mapMaybe srcSpanToRealSrcSpan-            . mconcat-              [ fmap getLoc . listify (only @(LHsDoc GhcPs)),-                fmap getLocA . listify isIEDocLike-              ]-            $ hsModule-          where-            isIEDocLike :: LIE GhcPs -> Bool-            isIEDocLike = \case-              L _ IEGroup {} -> True-              L _ IEDoc {} -> True-              L _ IEDocNamed {} -> True-              _ -> False+        epAnnCommentsToList = \case+          EpaComments cs -> cs+          EpaCommentsBalanced pcs fcs -> pcs <> fcs++    -- All spans of valid Haddock comments+    validHaddockCommentSpans =+      S.fromList+        . mapMaybe srcSpanToRealSrcSpan+        . mconcat+          [ fmap getLoc . listify (only @(LHsDoc GhcPs)),+            fmap getLocA . listify isIEDocLike+          ]+        $ hsModule+      where+        isIEDocLike :: LIE GhcPs -> Bool+        isIEDocLike = \case+          L _ IEGroup {} -> True+          L _ IEDoc {} -> True+          L _ IEDocNamed {} -> True+          _ -> False     only :: a -> Bool     only _ = True @@ -110,15 +146,15 @@ type LComment = RealLocated Comment  -- | A wrapper for a single comment. The 'Bool' indicates whether there were--- atoms before beginning of the comment in the original input. The--- 'NonEmpty' list inside contains lines of multiline comment @{\- … -\}@ or--- just single item\/line otherwise.+-- atoms before the beginning of the comment in the original input. The+-- 'NonEmpty' list inside contains the lines of a multiline comment+-- @{- … -}@, or just a single item\/line otherwise. data Comment = Comment Bool (NonEmpty Text)   deriving (Eq, Show, Data) --- | Normalize comment string. Sometimes one multi-line comment is turned--- into several lines for subsequent outputting with correct indentation for--- each line.+-- | Normalize a comment string. Sometimes a single multi-line comment is+-- split into several lines so that it can later be output with correct+-- indentation on each line. mkComment ::   -- | Lines of original input with their indices   [(Int, Text)] ->@@ -138,27 +174,85 @@                     then startIndent                     else T.length (T.takeWhile isSpace y)                 n = minimum (startIndent : fmap getIndent xs)-                commentPrefix = if "{-" `T.isPrefixOf` s then "" else "-- "-             in x :| ((commentPrefix <>) . escapeHaddockTriggers . T.drop n <$> xs)+             in x :| (escapeOpeningTrigger . T.drop n <$> xs)     (atomsBefore, ls') =       case dropWhile ((< commentLine) . fst) ls of         [] -> (False, [])         ((_, i) : ls'') ->-          case T.take 2 (T.stripStart i) of-            "--" -> (False, ls'')-            "{-" -> (False, ls'')-            _ -> (True, ls'')-    startIndent-      -- srcSpanStartCol counts columns starting from 1, so we subtract 1-      | "{-" `T.isPrefixOf` s = srcSpanStartCol l - 1-      -- For single-line comments, the only case where xs != [] is when an-      -- invalid haddock comment composed of several single-line comments is-      -- encountered. In that case, each line of xs is prefixed with an-      -- extra space (not present in the original comment), so we set-      -- startIndent = 1 to remove this space.-      | otherwise = 1+          let lineStart = T.stripStart i+              -- A pragma is code, not a comment, even though it opens the+              -- same way. Without this a comment trailing @{-# UNPACK #-}+              -- !Int@ looks as though nothing preceded it on the line.+              startsWithComment =+                "--" `T.isPrefixOf` lineStart+                  || ( "{-" `T.isPrefixOf` lineStart+                         && not ("{-#" `T.isPrefixOf` lineStart)+                     )+           in (not startsWithComment, ls'')+    -- srcSpanStartCol counts columns starting from 1, so we subtract 1.+    -- A multi-line run of @--@ lines reaches us as the source wrote it, so+    -- it is dedented the same way a block comment is.+    startIndent = srcSpanStartCol l - 1     commentLine = srcSpanStartLine l +-- | Turn the source text of a Haddock into a 'Comment'.+--+-- Only the indentation of the continuation lines is touched, so that the+-- comment can be re-indented along with the code it documents. Nothing is+-- re-prefixed and no Haddock triggers are escaped: the whole point is that+-- what the author wrote comes back out.+mkHaddockComment :: RealLocated Text -> Comment+mkHaddockComment (L l s) =+  -- Blank lines are kept: inside a doc comment they are the author's+  -- paragraph breaks, not the incidental spacing that 'removeConseqBlanks'+  -- tidies up between ordinary comment lines.+  Comment False . fmap T.stripEnd . spaceAfterTrigger $+    case NE.nonEmpty (T.lines s) of+      Nothing -> s :| []+      Just (x :| xs) ->+        let startIndent = srcSpanStartCol l - 1+            getIndent y =+              if T.all isSpace y+                then startIndent+                else T.length (T.takeWhile isSpace y)+            n = minimum (startIndent : fmap getIndent xs)+         in x :| fmap (T.drop n) xs++-- | Put a space between a Haddock's trigger and what follows it, so that+-- @-- |Foo@ comes out as @-- | Foo@.+--+-- Named anchors are left alone: the name in @-- $section@ is part of the+-- anchor, and a space would make it a different one.+spaceAfterTrigger :: NonEmpty Text -> NonEmpty Text+spaceAfterTrigger (x :| xs) =+  case go x of+    Nothing -> x :| xs+    -- The whole comment shifts right by one, not just the first line.+    -- Haddock drops a leading space from every line of a doc string when+    -- the first line has one, so padding the first line alone would take a+    -- space away from all the others.+    Just x' -> x' :| fmap indentContinuation xs+  where+    go t = do+      (o, afterOpener) <- opener t+      let (spaces, rest) = T.span (== ' ') afterOpener+      (trg, body) <- trigger rest+      if T.null body || " " `T.isPrefixOf` body+        then Nothing+        else Just (o <> spaces <> trg <> " " <> body)+    indentContinuation t = case opener t of+      Just (o, rest) -> o <> " " <> rest+      Nothing -> " " <> t+    opener t+      | Just rest <- T.stripPrefix "--" t = Just ("--", rest)+      | Just rest <- T.stripPrefix "{-" t = Just ("{-", rest)+      | otherwise = Nothing+    trigger t+      | Just body <- T.stripPrefix "|" t = Just ("|", body)+      | Just body <- T.stripPrefix "^" t = Just ("^", body)+      | (stars, body) <- T.span (== '*') t, not (T.null stars) = Just (stars, body)+      | otherwise = Nothing+ -- | Get a collection of lines from a 'Comment'. unComment :: Comment -> NonEmpty Text unComment (Comment _ xs) = xs@@ -175,7 +269,7 @@ ---------------------------------------------------------------------------- -- Helpers --- | Detect and extract stack header if it is present.+-- | Detect and extract the stack header if it is present. extractStackHeader ::   -- | Comment stream to analyze   [RealLocated Text] ->@@ -195,71 +289,123 @@ extractPragmas ::   -- | Input   Text ->+  -- | Where the file header ends, if the module has anything after it+  Maybe RealSrcLoc ->   -- | Comment stream to analyze   [RealLocated Text] ->   ([LComment], [([LComment], Pragma)])-extractPragmas input = go initialLs id id+extractPragmas input headerEnd = go initialLs id id   where     initialLs = zip [1 ..] (T.lines input)++    -- A pragma below the header is not a pragma as far as GHC is+    -- concerned, so it stays in the comment stream and is printed where it+    -- was written. Hoisting it would both give it an effect it did not+    -- have and drag every comment above it to the top of the module.+    inHeader x = case headerEnd of+      Nothing -> True+      Just end -> realSrcSpanStart (getRealSrcSpan x) < end+     go ls csSoFar pragmasSoFar = \case       [] -> (csSoFar [], pragmasSoFar [])       (x : xs) ->         case parsePragma (unRealSrcSpan x) of-          Nothing ->+          Just pragma+            | inHeader x ->+                let combined ys = (csSoFar ys, pragma)+                    go' ls' ys rest = go ls' id (pragmasSoFar . (combined ys :)) rest+                 in case xs of+                      [] -> go' ls [] xs+                      (y : ys) ->+                        let (ls', y') = mkComment ls y+                         in if onTheSameLine+                              (RealSrcSpan (getRealSrcSpan x) Strict.Nothing)+                              (RealSrcSpan (getRealSrcSpan y) Strict.Nothing)+                              then go' ls' [y'] ys+                              else go' ls [] xs+          _ ->             let (ls', x') = mkComment ls x              in go ls' (csSoFar . (x' :)) pragmasSoFar xs-          Just pragma ->-            let combined ys = (csSoFar ys, pragma)-                go' ls' ys rest = go ls' id (pragmasSoFar . (combined ys :)) rest-             in case xs of-                  [] -> go' ls [] xs-                  (y : ys) ->-                    let (ls', y') = mkComment ls y-                     in if onTheSameLine-                          (RealSrcSpan (getRealSrcSpan x) Strict.Nothing)-                          (RealSrcSpan (getRealSrcSpan y) Strict.Nothing)-                          then go' ls' [y'] ys-                          else go' ls [] xs  -- | Extract @'RealLocated' 'Text'@ from 'GHC.LEpaComment'.-unAnnotationComment :: GHC.LEpaComment -> Maybe (RealLocated Text)-unAnnotationComment (L epaLoc (GHC.EpaComment eck _)) =+unAnnotationComment :: Text -> GHC.LEpaComment -> Maybe (RealLocated Text)+unAnnotationComment input (L epaLoc (GHC.EpaComment eck _)) =   case eck of-    GHC.EpaDocComment s ->-      let trigger = case s of-            MultiLineDocString t _ -> Just t-            NestedDocString t _ -> Just t-            -- should not occur-            GeneratedDocString _ -> Nothing-       in haddock trigger (T.pack $ renderHsDocString s)+    -- A doc comment is taken from the source rather than rebuilt from the+    -- 'HsDocString' GHC parsed out of it: rebuilding cannot preserve a+    -- @{- | … -}@, nor an empty @-- |@, and losing either changes the AST.+    -- A comment that GHC lexed as a doc comment but that did not become+    -- part of the AST is not a Haddock at all. It still looks like one, so+    -- its trigger is escaped: Ormolu may move it somewhere a Haddock would+    -- be accepted, and it must not turn into one there.+    GHC.EpaDocComment _ ->+      withSpan $ \s ->+        Just (escapeOpeningTrigger (normalizeSpacing (sliceSpan input s)))     GHC.EpaDocOptions s -> mkL (T.pack s)-    GHC.EpaLineComment (T.pack -> s) -> mkL $-      case T.take 3 s of-        "-- " -> s-        "---" -> s-        _ -> insertAt " " s 3+    GHC.EpaLineComment (T.pack -> s) -> mkL (normalizeSpacing s)     GHC.EpaBlockComment s -> mkL (T.pack s)   where-    mkL = case epaLoc of-      GHC.EpaSpan (RealSrcSpan s _) -> Just . L s-      _ -> const Nothing-    insertAt x xs n = T.take (n - 1) xs <> x <> T.drop (n - 1) xs-    haddock mtrigger =-      mkL . dashPrefix . escapeHaddockTriggers . (trigger <>) <=< dropBlank-      where-        trigger = case mtrigger of-          Just HsDocStringNext -> "|"-          Just HsDocStringPrevious -> "^"-          Just (HsDocStringNamed n) -> "$" <> T.pack n-          Just (HsDocStringGroup k) -> T.replicate k "*"-          Nothing -> ""-        dashPrefix s = "--" <> spaceIfNecessary <> s-          where-            spaceIfNecessary = case T.uncons s of-              Just (c, _) | c /= ' ' -> " "-              _ -> ""-        dropBlank :: Text -> Maybe Text-        dropBlank s = if T.all isSpace s then Nothing else Just s+    realSpan = case epaLoc of+      GHC.EpaSpan (RealSrcSpan s _) -> Just s+      _ -> Nothing+    mkL = case realSpan of+      Just s -> Just . L s+      Nothing -> const Nothing+    withSpan f = do+      s <- realSpan+      L s <$> f s++-- | Put a space after the dashes of a line comment when there is none.+--+-- This is the one normalization that survives: @--foo@ becomes @-- foo@ and+-- @--|foo@ becomes @-- |foo@, which is what one expects of a formatter.+-- Everything else about a comment is left as it was written.+normalizeSpacing :: Text -> Text+normalizeSpacing s+  | not ("--" `T.isPrefixOf` s) = s+  | otherwise = case T.uncons (T.drop 2 s) of+      Nothing -> s+      Just (c, _)+        | c == ' ' || c == '-' -> s+        | otherwise -> "-- " <> T.drop 2 s++-- | Escape a Haddock trigger that opens a comment line, so that the line+-- cannot be read as a Haddock wherever it ends up.+--+-- A line that does not open a comment is left alone: a @*@ in the middle of+-- a @{- … -}@ block is just a character, and escaping it there only+-- disfigures the text.+escapeOpeningTrigger :: Text -> Text+escapeOpeningTrigger t =+  case T.stripPrefix "--" t of+    Just rest -> "--" <> escapeAfterSpaces rest+    Nothing -> case T.stripPrefix "{-" t of+      Just rest -> "{-" <> escapeAfterSpaces rest+      Nothing -> t+  where+    escapeAfterSpaces x =+      let (spaces, rest) = T.span (== ' ') x+       in spaces <> escapeHaddockTriggers rest++-- | Extract the source text a span covers.+sliceSpan :: Text -> RealSrcSpan -> Text+sliceSpan input spn =+  case spannedLines of+    [] -> ""+    [single] -> T.take (endCol - startCol) (T.drop (startCol - 1) single)+    (firstLine : rest) ->+      T.intercalate "\n" $+        T.drop (startCol - 1) firstLine : trimLast rest+  where+    startLine = srcSpanStartLine spn+    endLine = srcSpanEndLine spn+    startCol = srcSpanStartCol spn+    endCol = srcSpanEndCol spn+    spannedLines =+      take (endLine - startLine + 1) (drop (startLine - 1) (T.lines input))+    trimLast xs = case reverse xs of+      [] -> []+      (y : ys) -> reverse (T.take (endCol - 1) y : ys)  -- | Remove consecutive blank lines. removeConseqBlanks :: NonEmpty Text -> NonEmpty Text
src/Ormolu/Parser/Pragma.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} --- | A module for parsing of pragmas from comments.+-- | A module for parsing pragmas from comments. module Ormolu.Parser.Pragma   ( Pragma (..),     parsePragma,
src/Ormolu/Parser/Result.hs view
@@ -1,16 +1,20 @@--- | A type for result of parsing.+-- | A type for the result of parsing. module Ormolu.Parser.Result   ( SourceSnippet (..),     ParseResult (..),+    inputComments,   ) where +import Data.List (sortOn)+import Data.Maybe (maybeToList) import Data.Set (Set) import Data.Text (Text) import Distribution.ModuleName (ModuleName) import GHC.Data.EnumSet (EnumSet) import GHC.Hs (GhcPs, HsModule) import GHC.LanguageExtensions.Type+import GHC.Types.SrcLoc (getLoc) import Ormolu.Config (SourceType) import Ormolu.Fixity (ModuleFixityMap) import Ormolu.Parser.CommentStream@@ -23,7 +27,7 @@ data ParseResult = ParseResult   { -- | Parsed module or signature     prParsedSource :: HsModule GhcPs,-    -- | Either regular module or signature file+    -- | Whether this is a regular module or a signature file     prSourceType :: SourceType,     -- | Stack header     prStackHeader :: Maybe LComment,@@ -31,12 +35,29 @@     prPragmas :: [([LComment], Pragma)],     -- | Comment stream     prCommentStream :: CommentStream,+    -- | Source text of the module's Haddocks, keyed by span+    prHaddockText :: HaddockText,     -- | Enabled extensions     prExtensions :: EnumSet Extension,     -- | Fixity map for operators     prModuleFixityMap :: ModuleFixityMap,-    -- | Indentation level, can be non-zero in case of region formatting+    -- | Indentation level; can be non-zero in the case of region formatting     prIndent :: Int,     -- | Local modules     prLocalModules :: Set ModuleName   }++-- | All the comments a snippet started with, in source order.+--+-- This is not simply the comment stream: the Stack header and the comments+-- that precede pragmas are lifted out of the stream while parsing, and are+-- emitted separately. Haddocks, on the other hand, are not included at all,+-- because GHC's parser makes them part of the AST.+inputComments :: ParseResult -> [LComment]+inputComments ParseResult {prStackHeader, prPragmas, prCommentStream} =+  sortOn getLoc $+    maybeToList prStackHeader+      <> concatMap fst prPragmas+      <> streamComments+  where+    CommentStream streamComments = prCommentStream
src/Ormolu/Printer.hs view
@@ -1,48 +1,111 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-}  -- | Pretty-printer for Haskell AST.+--+-- Each snippet is rendered twice. Comments are attached to the elements the+-- printer enters, and the only way to know which elements those are is to+-- render once and see; the first pass therefore runs with no comments at+-- all and is kept only for the spans it visited. See 'render'. module Ormolu.Printer   ( printSnippets,+    printSnippetsWithPlacements,     PrinterOpts (..),   ) where +import Data.Choice (Choice) import Data.Text (Text) import Data.Text qualified as T+import GHC.Types.SrcLoc (RealSrcSpan)+import Ormolu.Comments.Anchor import Ormolu.Config+import Ormolu.Parser.CommentStream (CommentStream (..)) import Ormolu.Parser.Result import Ormolu.Printer.Combinators+import Ormolu.Printer.CommentPlacement import Ormolu.Printer.Meat.Module-import Ormolu.Printer.SpanStream import Ormolu.Processing.Common  -- | Render several source snippets. printSnippets ::+  PrinterOptsTotal ->   -- | Whether to print out debug information during printing-  Bool ->+  Choice "debug" ->   -- | Result of parsing   [SourceSnippet] ->-  PrinterOptsTotal ->   -- | Resulting rendition   Text-printSnippets debug snippets printerOpts = T.concat . fmap printSnippet $ snippets+printSnippets printerOpts debug = T.concat . fmap fst . printSnippetsWithPlacements printerOpts debug++-- | Like 'printSnippets', but also return, for each snippet, the placement+-- of every comment it emitted.+--+-- Snippets are rendered separately and their spans are relative to+-- themselves, so the placements stay grouped by snippet: anything that+-- compares them against the input has to work one snippet at a time.+printSnippetsWithPlacements ::+  PrinterOptsTotal ->+  -- | Whether to print out debug information during printing+  Choice "debug" ->+  -- | Result of parsing+  [SourceSnippet] ->+  -- | For each snippet, its rendition and the comments it emitted+  [(Text, [CommentPlacement])]+printSnippetsWithPlacements printerOpts debug = fmap (renderSnippet printerOpts debug)++-- | Render one snippet. A snippet that could not be parsed is passed+-- through as it was.+renderSnippet ::+  PrinterOptsTotal ->+  Choice "debug" ->+  SourceSnippet ->+  (Text, [CommentPlacement])+renderSnippet printerOpts debug = \case+  ParsedSnippet r -> render printerOpts debug r+  RawSnippet r -> (r, [])++-- | Render one parsed snippet, along with the placement of every comment it+-- emitted.+--+-- This renders twice. Anchoring a comment to an element the printer never+-- enters would leave the comment stranded, and there is no way to know+-- which elements those are but to render once and see. The first pass is+-- given an empty 'AnchorMap', so it emits no comments and its output is+-- thrown away; what it is for is the spans it visited, which is what the+-- second pass attaches the comments to.+render ::+  PrinterOptsTotal ->+  Choice "debug" ->+  ParseResult ->+  (Text, [CommentPlacement])+render printerOpts debug r@ParseResult {..} =+  let (_, _, visited) = renderWith noComments+      (rendered, placements, _) = renderWith (anchorMapFor r visited)+   in (rendered, placements)   where-    printSnippet = \case-      ParsedSnippet ParseResult {..} ->-        reindent prIndent $-          runR-            ( p_hsModule-                prStackHeader-                prPragmas-                prParsedSource-            )-            (mkSpanStream prParsedSource)-            prCommentStream-            printerOpts-            prLocalModules-            prSourceType-            prExtensions-            prModuleFixityMap-            debug-      RawSnippet r -> r+    renderWith anchorMap =+      let (rendered, placements, visited) =+            runR+              ( p_hsModule+                  prStackHeader+                  prPragmas+                  prParsedSource+              )+              anchorMap+              printerOpts+              prLocalModules+              prSourceType+              prExtensions+              prModuleFixityMap+              debug+              prHaddockText+       in (reindent prIndent rendered, placements, visited)++-- | Attach the comments of a snippet to the elements the printer enters.+anchorMapFor :: ParseResult -> [RealSrcSpan] -> AnchorMap+anchorMapFor ParseResult {..} visited =+  mkAnchorMap (attachComments comments visited)+  where+    CommentStream comments = prCommentStream
src/Ormolu/Printer/Combinators.hs view
@@ -3,15 +3,15 @@ {-# LANGUAGE OverloadedStrings #-}  -- | Printing combinators. The definitions here are presented in such an--- order so you can just go through the Haddocks and by the end of the file--- you should have a pretty good idea how to program rendering logic.+-- order that you can just read through the Haddocks, and by the end of the+-- file you should have a pretty good idea of how to program rendering logic. module Ormolu.Printer.Combinators   ( -- * The 'R' monad     R,     runR,     getEnclosingSpan,-    getEnclosingSpanWhere,-    getEnclosingComments,+    getCommentsAnchoredWithin,+    getCommentsBefore,     isExtensionEnabled,      -- * Combinators@@ -24,6 +24,7 @@     newline,     declNewline,     multilineCommentNewline,+    newlineLiteral,     inci,     inciBy,     inciIf,@@ -32,11 +33,13 @@     askModuleFixityMap,     askDebug,     located,-    encloseLocated,+    locatedEmpty,     located',     switchLayout,+    switchLayoutWithEnclosingComments,     switchLayoutNoLimit,     spansLayout,+    enterLayout,     Layout (..),     vlayout,     getLayout,@@ -48,6 +51,7 @@     -- ** Formatting lists     sep,     sepSemi,+    sepSemi',     canUseBraces,     useBraces,     dontUseBraces,@@ -58,6 +62,7 @@     backticks,     banana,     braces,+    recordBraces,     brackets,     parens,     parensHash,@@ -68,7 +73,6 @@     comma,     commaDel,     commaDelImportExport,-    equals,     token'Larrowtail,     token'Rarrowtail,     token'darrow,@@ -86,12 +90,15 @@     token'lolly,      -- ** Stateful markers-    SpanMark (..),-    spanMarkSpan,+    LastEmitted (..),+    lastEmittedSpan,     HaddockStyle (..),-    setSpanMark,-    getSpanMark,+    setLastEmitted,+    getLastEmitted, +    -- ** Haddocks+    lookupHaddockText,+     -- ** Placement     Placement (..),     placeHanging,@@ -100,6 +107,7 @@  import Control.Monad import Data.List (intersperse)+import Data.List.NonEmpty qualified as NE import Data.Text (Text) import GHC.Data.Strict qualified as Strict import GHC.LanguageExtensions.Type@@ -108,6 +116,7 @@ import Ormolu.Config import Ormolu.Printer.Comments import Ormolu.Printer.Internal+import Ormolu.Utils (combineSrcSpans')  ---------------------------------------------------------------------------- -- Basic@@ -122,7 +131,7 @@ inciIf b m = if b then inci m else m  -- | Enter a 'GenLocated' entity. This combinator handles outputting comments--- and sets layout (single-line vs multi-line) for the inner computation.+-- and sets the layout (single-line vs multi-line) for the inner computation. -- Roughly, the rule for using 'located' is that every time there is a -- 'Located' wrapper, it should be “discharged” with a corresponding -- 'located' invocation.@@ -130,59 +139,67 @@   (HasLoc l) =>   -- | Thing to enter   GenLocated l a ->-  -- | How to render inner value+  -- | How to render the inner value   (a -> R ()) ->   R () located (L l' a) f = case locA l' of   UnhelpfulSpan _ -> f a   RealSrcSpan l _ -> do+    recordVisitedSpan l     spitPrecedingComments l     withEnclosingSpan l $       switchLayout [RealSrcSpan l Strict.Nothing] (f a)     spitFollowingComments l --- | Similar to 'located', but when the "payload" is an empty list, print--- virtual elements at the start and end of the source span to prevent comments--- from "floating out".-encloseLocated ::-  (HasLoc l) =>-  GenLocated l [a] ->-  ([a] -> R ()) ->+-- | Give an empty bracketed construct something for a comment written+-- inside it to attach to.+--+-- Brackets are rendered with 'txt', so an empty export or import list, an+-- empty @[]@ or a record with no fields contains no element at all. A+-- comment written between the brackets would be attached to whatever+-- encloses them and rendered outside them, so a zero-width element is+-- entered at the opening bracket instead.+locatedEmpty ::+  -- | Span of the empty construct+  SrcSpan ->   R ()-encloseLocated la f = located la $ \a -> do-  when (null a) $ located (L startSpan ()) pure-  f a-  when (null a) $ located (L endSpan ()) pure-  where-    l = locA la-    (startLoc, endLoc) = (srcSpanStart l, srcSpanEnd l)-    (startSpan, endSpan) = (mkSrcSpan startLoc startLoc, mkSrcSpan endLoc endLoc)+locatedEmpty l =+  let loc = srcSpanStart l+   in located (L (mkSrcSpan loc loc) ()) pure --- | A version of 'located' with arguments flipped.+-- | A version of 'located' with the arguments flipped. located' ::   (HasLoc l) =>-  -- | How to render inner value+  -- | How to render the inner value   (a -> R ()) ->   -- | Thing to enter   GenLocated l a ->   R () located' = flip located --- | Set layout according to combination of given 'SrcSpan's for a given.--- Use this only when you need to set layout based on e.g. combined span of--- several elements when there is no corresponding 'Located' wrapper--- provided by GHC AST. It is relatively rare that this one is needed.+-- | Set the layout according to the combination of the given 'SrcSpan's,+-- together with the spans of the comments that belong inside them. ----- Given empty list this function will set layout to single line.+-- Comments count towards the layout: a construct that would fit on one line+-- has to be broken up anyway if a comment was written inside it, or the+-- comment would swallow whatever follows it on the line.+--+-- 'located' calls this for you. Call it directly only when the layout has+-- to come from something the GHC AST has no 'Located' wrapper for, such as+-- the combined span of several elements; that is rare.+--+-- Given an empty list and no comments, this function will set the layout to+-- single-line. switchLayout ::   -- | Span that controls layout   [SrcSpan] ->   -- | Computation to run with changed layout   R () ->   R ()-switchLayout spans r = do-  layout <- spansLayout spans-  enterLayout layout r+switchLayout spans' m = do+  csSpans <- commentSpansIn (combineSrcSpans' <$> NE.nonEmpty spans')+  layout <- spansLayout (spans' <> csSpans)+  enterLayout layout m  -- | Same as 'switchLayout', except disregards the column limit. --@@ -191,7 +208,57 @@ switchLayoutNoLimit :: [SrcSpan] -> R () -> R () switchLayoutNoLimit spans = enterLayout (spansLayoutWithLimit NoLimit spans) --- | Which layout combined spans result in?+-- | Like 'switchLayout', but the comments are looked for in the enclosing+-- element rather than in the given spans.+--+-- This is what a bracketed construct needs. In+--+-- > ( -- c+-- >   x+-- > )+--+-- the comment sits between the bracket and @x@, so it is inside neither of+-- them, and the parentheses would be put on one line despite it. Widening+-- the question to the enclosing element catches it. Do not reach for this+-- elsewhere: it is deliberately coarser than 'switchLayout', and applying+-- it where the enclosing element is large would let one comment break every+-- layout decision inside it.+switchLayoutWithEnclosingComments ::+  -- | Span that controls layout+  [SrcSpan] ->+  -- | Computation to run with changed layout+  R () ->+  R ()+switchLayoutWithEnclosingComments spans' m = do+  enclosing <- getEnclosingSpan+  csSpans <- commentSpansIn (flip RealSrcSpan Strict.Nothing <$> enclosing)+  layout <- spansLayout (spans' <> csSpans)+  enterLayout layout m++-- | The spans of the comments that belong inside the given region: both+-- attached to something in it and written inside it.+--+-- Both halves are needed. Without the first, a comment anywhere in a+-- declaration would force every layout decision inside that declaration to+-- multi-line. Without the second, a comment trailing an element would force+-- that element itself to be broken up.+--+-- Haddocks are not consulted here. They do not travel in the anchor map,+-- and their spans sit where the author wrote them rather than where they+-- will be printed, which is the wrong question; see+-- 'Ormolu.Printer.Meat.Common.multiLineIfDocumented'.+commentSpansIn :: Maybe SrcSpan -> R [SrcSpan]+commentSpansIn = \case+  Just (RealSrcSpan region _) -> do+    comments <- getCommentsAnchoredWithin region+    pure+      [ RealSrcSpan spn Strict.Nothing+      | L spn _ <- comments,+        region `containsSpan` spn+      ]+  _ -> pure []++-- | Which layout do the combined spans result in? spansLayout :: [SrcSpan] -> R Layout spansLayout spans = do   colLimit <- getPrinterOpt poColumnLimit@@ -213,14 +280,14 @@            in spanLineLength > fromIntegral maxLineLength         _ -> False --- | Insert a space if enclosing layout is single-line, or newline if it's--- multiline.+-- | Insert a space if the enclosing layout is single-line, or a newline if+-- it is multi-line. -- -- > breakpoint = vlayout space newline breakpoint :: R () breakpoint = vlayout space newline --- | Similar to 'breakpoint' but outputs nothing in case of single-line+-- | Similar to 'breakpoint', but outputs nothing in the case of single-line -- layout. -- -- > breakpoint' = vlayout (return ()) newline@@ -230,7 +297,7 @@ ---------------------------------------------------------------------------- -- Formatting lists --- | Render a collection of elements inserting a separator between them.+-- | Render a collection of elements, inserting a separator between them. sep ::   -- | Separator   R () ->@@ -241,9 +308,9 @@   R () sep s f xs = sequence_ (intersperse s (f <$> xs)) --- | Render a collection of elements layout-sensitively using given printer,--- inserting semicolons if necessary and respecting 'useBraces' and--- 'dontUseBraces' combinators.+-- | Render a collection of elements layout-sensitively using the given+-- printer, inserting semicolons if necessary and respecting the 'useBraces'+-- and 'dontUseBraces' combinators. -- -- > useBraces $ sepSemi txt ["foo", "bar"] -- >   == vlayout (txt "{ foo; bar }") (txt "foo\nbar")@@ -256,7 +323,25 @@   -- | Elements to render   [a] ->   R ()-sepSemi f xs = vlayout singleLine multiLine+sepSemi = sepSemi' False++-- | A version of 'sepSemi' that allows one to control whether semicolons+-- should be inserted in multi-line layout.+--+-- > useBraces $ sepSemi' False txt ["foo", "bar"]+-- >   == vlayout (txt "{ foo; bar }") (txt "foo\nbar")+--+-- > dontUseBraces $ sepSemi' True txt ["foo", "bar"]+-- >   == vlayout (txt "foo; bar") (txt "foo;\nbar")+sepSemi' ::+  -- | Whether to insert semicolons in multi-line layout+  Bool ->+  -- | How to render an element+  (a -> R ()) ->+  -- | Elements to render+  [a] ->+  R ()+sepSemi' addMultiColSemi f xs = vlayout singleLine multiLine   where     singleLine = do       ub <- canUseBraces@@ -272,12 +357,15 @@               txt "}"             else sep (txt ";" >> space) f xs'     multiLine =-      sep newline (dontUseBraces . f) xs+      sep+        (if addMultiColSemi then txt ";" >> newline else newline)+        (dontUseBraces . f)+        xs  ---------------------------------------------------------------------------- -- Wrapping --- | 'BracketStyle' controlling how closing bracket is rendered.+-- | 'BracketStyle' controlling how the closing bracket is rendered. data BracketStyle   = -- | Normal     N@@ -285,30 +373,31 @@     S   deriving (Eq, Show) --- | Surround given entity by backticks.+-- | Surround the given entity with backticks. backticks :: R () -> R () backticks m = do   txt "`"   m   txt "`" --- | Surround given entity by banana brackets (i.e., from arrow notation.)+-- | Surround the given entity with banana brackets (i.e. from arrow+-- notation). banana :: BracketStyle -> R () -> R () banana = brackets_ True token'oparenbar token'cparenbar --- | Surround given entity by curly braces @{@ and  @}@.+-- | Surround the given entity with curly braces @{@ and @}@. braces :: BracketStyle -> R () -> R () braces = brackets_ False (txt "{") (txt "}") --- | Surround given entity by square brackets @[@ and @]@.+-- | Surround the given entity with square brackets @[@ and @]@. brackets :: BracketStyle -> R () -> R () brackets = brackets_ False (txt "[") (txt "]") --- | Surround given entity by parentheses @(@ and @)@.+-- | Surround the given entity with parentheses @(@ and @)@. parens :: BracketStyle -> R () -> R () parens = brackets_ False (txt "(") (txt ")") --- | Surround given entity by @(# @ and @ #)@.+-- | Surround the given entity with @(# @ and @ #)@. parensHash :: BracketStyle -> R () -> R () parensHash = brackets_ True (txt "(#") (txt "#)") @@ -369,6 +458,31 @@       newline       inciIf (style == S) close +-- With leading commas align the close brace with commas+-- otherwise move the close brace back to the left+recordBraces :: R () -> R ()+recordBraces m = do+  commaStyle <- getPrinterOpt poCommaStyle+  recordBraces_ (commaStyle == Trailing) m++recordBraces_ :: Bool -> R () -> R ()+recordBraces_ moveBraceBack m = do+  style <- getPrinterOpt poRecordStyle+  case style of+    RecordStyleAligned -> braces N m+    RecordStyleKnr ->+      vlayout+        (txt "{" >> m >> txt "}")+        ( do+            txt "{"+            newline+            sitcc m+            newline+            if moveBraceBack+              then inciByFrac (-1) (txt "}")+              else txt "}"+        )+ ---------------------------------------------------------------------------- -- Literals @@ -394,10 +508,6 @@   Leading -> breakpoint' >> comma >> space   Trailing -> comma >> breakpoint --- | Print @=@. Do not use @'txt' "="@.-equals :: R ()-equals = interferingTxt "="- ---------------------------------------------------------------------------- -- Token literals -- The names of the following literals are from GHC's@@ -477,13 +587,13 @@ -- Placement  -- | Expression placement. This marks the places where expressions that--- implement handing forms may use them.+-- support hanging forms may use them. data Placement   = -- | Multi-line layout should cause-    -- insertion of a newline and indentation-    -- bump+    -- insertion of a newline and an+    -- indentation bump     Normal-  | -- | Expressions that have hanging form+  | -- | Expressions that have a hanging form     -- should use it and avoid bumping one level     -- of indentation     Hanging
+ src/Ormolu/Printer/CommentPlacement.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++-- | A record of where each comment ended up in the rendered output.+--+-- The printer notes every comment as it emits it. Ormolu then checks that+-- record against the comments of the input, which is how it can promise+-- that formatting neither drops, duplicates, invents nor reorders a+-- comment; see "Ormolu.Comments.Invariants".+module Ormolu.Printer.CommentPlacement+  ( CommentPlacement (..),+    CommentSlot (..),+    slotAnchor,+  )+where++import GHC.Types.SrcLoc++----------------------------------------------------------------------------+-- Types++-- | Where a comment ended up relative to the AST element it was attached+-- to.+--+-- Only the distinctions a consumer can act on are kept: whether the comment+-- was attached to an element, and whether it rode along with a pragma. See+-- "Ormolu.Comments.Invariants", which is what reads this.+data CommentSlot+  = -- | Attached to the element at this span+    SlotAt RealSrcSpan+  | -- | Hoisted into the module header along with a pragma. Pragmas are+    -- sorted on purpose, so the order such a comment comes out in says+    -- nothing.+    SlotPragma+  | -- | Attached to nothing: the Stack header, or a leftover flushed at the+    -- end of the module by 'Ormolu.Printer.Comments.spitRemainingComments'+    SlotFloating+  deriving (Eq, Show)++-- | The span of the AST element that a comment was attached to, if the+-- comment was attached to an element at all.+slotAnchor :: CommentSlot -> Maybe RealSrcSpan+slotAnchor = \case+  SlotAt spn -> Just spn+  SlotPragma -> Nothing+  SlotFloating -> Nothing++-- | A single placement decision: one comment and the slot it was rendered+-- in.+data CommentPlacement = CommentPlacement+  { -- | Span of the comment in the input, which is what identifies it+    cpSpan :: RealSrcSpan,+    -- | Where the comment ended up+    cpSlot :: CommentSlot+  }+  deriving (Eq, Show)
src/Ormolu/Printer/Comments.hs view
@@ -1,6 +1,7 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} --- | Helpers for formatting of comments. This is low-level code, use+-- | Helpers for formatting comments. This is low-level code; use -- "Ormolu.Printer.Combinators" unless you know what you are doing. module Ormolu.Printer.Comments   ( spitPrecedingComments,@@ -8,6 +9,7 @@     spitRemainingComments,     spitCommentNow,     spitCommentPending,+    CommentSlot (..),   ) where @@ -15,151 +17,130 @@ import Data.List.NonEmpty qualified as NE import Data.Maybe (listToMaybe) import GHC.Types.SrcLoc+import Ormolu.Comments.Anchor import Ormolu.Parser.CommentStream+import Ormolu.Printer.CommentPlacement import Ormolu.Printer.Internal  ---------------------------------------------------------------------------- -- Top-level --- | Output all preceding comments for an element at given location.+-- | Output all preceding comments for an element at the given location. spitPrecedingComments ::   -- | Span of the element to attach comments to   RealSrcSpan ->   R () spitPrecedingComments ref = do-  comments <- handleCommentSeries (spitPrecedingComment ref)-  when (not $ null comments) $ do-    lastMark <- getSpanMark+  comments <- withAnchorMap (claimBefore ref)+  forM_ comments (spitPrecedingComment ref)+  unless (null comments) $ do+    lastEmitted <- getLastEmitted     -- Insert a blank line between the preceding comments and the thing     -- after them if there was a blank line in the input.-    when (needsNewlineBefore ref lastMark) newline+    when (needsNewlineBefore ref lastEmitted) newline --- | Output all comments following an element at given location.+-- | Output all comments following an element at the given location. spitFollowingComments ::   -- | Span of the element to attach comments to   RealSrcSpan ->   R () spitFollowingComments ref = do-  trimSpanStream ref-  void $ handleCommentSeries (spitFollowingComment ref)+  comments <- withAnchorMap (claimTrailing ref)+  forM_ comments (spitFollowingComment ref) --- | Output all remaining comments in the comment stream.+-- | Output every comment that no element claimed.+--+-- This is the safety net that keeps a misattached comment from being lost+-- outright. It also means misattachment is silent, which is why+-- "Ormolu.Comments.Invariants" exists. spitRemainingComments :: R () spitRemainingComments = do-  -- Make sure we have a blank a line between the last definition and the+  -- Make sure we have a blank line between the last definition and the   -- trailing comments.   newline-  void $ handleCommentSeries spitRemainingComment+  comments <- withAnchorMap claimRemaining+  forM_ comments spitRemainingComment  ---------------------------------------------------------------------------- -- Single-comment functions --- | Output a single preceding comment for an element at given location.+-- | Output a single preceding comment for an element at the given location. spitPrecedingComment ::-  -- | Span of the element to attach comments to+  -- | Span of the element the comment is attached to   RealSrcSpan ->-  -- | The comment that was output, if any-  R (Maybe LComment)-spitPrecedingComment ref = do-  mlastMark <- getSpanMark-  let p (L l _) = realSrcSpanEnd l <= realSrcSpanStart ref-  withPoppedComment p $ \l comment -> do-    lineSpans <- thisLineSpans-    let thisCommentLine = srcLocLine (realSrcSpanStart l)-        needsNewline =-          case listToMaybe lineSpans of-            Nothing -> False-            Just spn -> srcLocLine (realSrcSpanEnd spn) /= thisCommentLine-    when (needsNewline || needsNewlineBefore l mlastMark) newline-    spitCommentNow l comment-    if theSameLinePre l ref-      then space-      else newline+  -- | The comment to output+  LComment ->+  R ()+spitPrecedingComment ref (L l comment) = do+  lastEmitted <- getLastEmitted+  lineSpans <- thisLineSpans+  let thisCommentLine = srcLocLine (realSrcSpanStart l)+      needsNewline =+        case listToMaybe lineSpans of+          Nothing -> False+          Just spn -> srcLocLine (realSrcSpanEnd spn) /= thisCommentLine+      sameLine = theSameLinePre l ref+  when (needsNewline || needsNewlineBefore l lastEmitted) newline+  spitCommentNow (SlotAt ref) l comment+  if sameLine+    then space+    else newline --- | Output a comment that follows element at given location immediately on--- the same line, if there is any.+-- | Output a single comment that follows an element at the given location. spitFollowingComment ::-  -- | AST element to attach comments to+  -- | Span of the element the comment is attached to   RealSrcSpan ->-  -- | The comment that was output, if any-  R (Maybe LComment)-spitFollowingComment ref = do-  mlastMark <- getSpanMark-  mnSpn <- nextEltSpan-  -- Get first enclosing span that is not equal to reference span, i.e. it's-  -- truly something enclosing the AST element.-  meSpn <- getEnclosingSpanWhere (/= ref)-  withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastMark) $ \l comment ->-    if theSameLinePost l ref-      then-        if isMultilineComment comment-          then space >> spitCommentNow l comment-          else spitCommentPending OnTheSameLine l comment-      else do-        when (needsNewlineBefore l mlastMark) $-          registerPendingCommentLine OnNextLine ""-        spitCommentPending OnNextLine l comment+  -- | The comment to output+  LComment ->+  R ()+spitFollowingComment ref (L l comment) = do+  lastEmitted <- getLastEmitted+  if theSameLinePost l ref+    then+      if isMultilineComment comment+        then space >> spitCommentNow (SlotAt ref) l comment+        else spitCommentPending (SlotAt ref) OnTheSameLine l comment+    else do+      -- A comment keeps the blank line the input had in front of it. When+      -- nothing carrying a position has been emitted since, the element the+      -- comment is attached to is what that blank line separated it from.+      let lastEmitted' = case lastEmittedSpan lastEmitted of+            Just _ -> lastEmitted+            Nothing -> LastEmittedComment ref+      when (needsNewlineBefore l lastEmitted') $+        registerPendingCommentLine OnNextLine ""+      spitCommentPending (SlotAt ref) OnNextLine l comment --- | Output a single remaining comment from the comment stream.+-- | Output a single unclaimed comment. spitRemainingComment ::-  -- | The comment that was output, if any-  R (Maybe LComment)-spitRemainingComment = do-  mlastMark <- getSpanMark-  withPoppedComment (const True) $ \l comment -> do-    when (needsNewlineBefore l mlastMark) newline-    spitCommentNow l comment-    newline+  -- | The comment to output+  LComment ->+  R ()+spitRemainingComment (L l comment) = do+  lastEmitted <- getLastEmitted+  when (needsNewlineBefore l lastEmitted) newline+  spitCommentNow SlotFloating l comment+  newline  ---------------------------------------------------------------------------- -- Helpers --- | Output series of comments.-handleCommentSeries ::-  -- | Output and return the next comment, if any-  R (Maybe LComment) ->-  -- | The comments outputted-  R [LComment]-handleCommentSeries f = go-  where-    go = do-      mComment <- f-      case mComment of-        Nothing -> return []-        Just comment -> (comment :) <$> go---- | Try to pop a comment using given predicate and if there is a comment--- matching the predicate, print it out.-withPoppedComment ::-  -- | Comment predicate-  (LComment -> Bool) ->-  -- | Printing function-  (RealSrcSpan -> Comment -> R ()) ->-  -- | Are we done?-  R (Maybe LComment)-withPoppedComment p f = do-  r <- popComment p-  case r of-    Nothing -> return ()-    Just (L l comment) -> f l comment-  return r---- | Determine if we need to insert a newline between current comment and--- last printed comment.+-- | Determine whether we need to insert a newline between the current+-- comment and the last printed comment. needsNewlineBefore ::   -- | Current comment span   RealSrcSpan ->-  -- | Last printed comment span-  Maybe SpanMark ->+  -- | What was emitted last+  LastEmitted ->   Bool-needsNewlineBefore _ (Just (HaddockSpan _ _)) = True-needsNewlineBefore l mlastMark =-  case spanMarkSpan <$> mlastMark of+needsNewlineBefore _ (LastEmittedHaddock _) = True+needsNewlineBefore l lastEmitted =+  case lastEmittedSpan lastEmitted of     Nothing -> False-    Just lastMark ->-      srcSpanStartLine l > srcSpanEndLine lastMark + 1+    Just lastSpn ->+      srcSpanStartLine l > srcSpanEndLine lastSpn + 1 --- | Is the preceding comment and AST element are on the same line?+-- | Are the preceding comment and the AST element on the same line? theSameLinePre ::   -- | Current comment span   RealSrcSpan ->@@ -169,7 +150,7 @@ theSameLinePre l ref =   srcSpanEndLine l == srcSpanStartLine ref --- | Is the following comment and AST element are on the same line?+-- | Are the following comment and the AST element on the same line? theSameLinePost ::   -- | Current comment span   RealSrcSpan ->@@ -179,99 +160,40 @@ theSameLinePost l ref =   srcSpanStartLine l == srcSpanEndLine ref --- | Determine if given comment follows AST element.-commentFollowsElt ::-  -- | Location of AST element-  RealSrcSpan ->-  -- | Location of next AST element-  Maybe RealSrcSpan ->-  -- | Location of enclosing AST element-  Maybe RealSrcSpan ->-  -- | Location of last comment in the series-  Maybe SpanMark ->-  -- | Comment to test-  LComment ->-  Bool-commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) =-  -- A comment follows a AST element if all 4 conditions are satisfied:-  goesAfter-    && logicallyFollows-    && noEltBetween-    && (continuation || lastInEnclosing || supersedesParentElt)-  where-    -- 1) The comment starts after end of the AST element:-    goesAfter =-      realSrcSpanStart l >= realSrcSpanEnd ref-    -- 2) The comment logically belongs to the element, four cases:-    logicallyFollows =-      theSameLinePost l ref -- a) it's on the same line-        || continuation -- b) it's a continuation of a comment block-        || lastInEnclosing -- c) it's the last element in the enclosing construct--    -- 3) There is no other AST element between this element and the comment:-    noEltBetween =-      case mnSpn of-        Nothing -> True-        Just nspn ->-          realSrcSpanStart nspn >= realSrcSpanEnd l-    -- 4) Less obvious: if column of comment is closer to the start of-    -- enclosing element, it probably related to that parent element, not to-    -- the current child element. This rule is important because otherwise-    -- all comments would end up assigned to closest inner elements, and-    -- parent elements won't have a chance to get any comments assigned to-    -- them. This is not OK because comments will get indented according to-    -- the AST elements they are attached to.-    ---    -- Skip this rule if the comment is a continuation of a comment block.-    supersedesParentElt =-      case meSpn of-        Nothing -> True-        Just espn ->-          let startColumn = srcLocCol . realSrcSpanStart-           in startColumn espn > startColumn ref-                || ( abs (startColumn espn - startColumn l)-                       >= abs (startColumn ref - startColumn l)-                   )-    continuation =-      -- A comment is a continuation when it doesn't have non-whitespace-      -- lexemes in front of it and goes right after the previous comment.-      not (hasAtomsBefore comment)-        && ( case mlastMark of-               Just (HaddockSpan _ _) -> False-               Just (CommentSpan spn) ->-                 srcSpanEndLine spn + 1 == srcSpanStartLine l-               _ -> False-           )-    lastInEnclosing =-      case meSpn of-        -- When there is no enclosing element, return false-        Nothing -> False-        -- When there is an enclosing element,-        Just espn ->-          let -- Make sure that the comment is inside the enclosing element-              insideParent = realSrcSpanEnd l <= realSrcSpanEnd espn-              -- And check if the next element is outside of the parent-              nextOutsideParent = case mnSpn of-                Nothing -> True-                Just nspn -> realSrcSpanEnd espn < realSrcSpanStart nspn-           in insideParent && nextOutsideParent- -- | Output a 'Comment' immediately. This is a low-level printing function.-spitCommentNow :: RealSrcSpan -> Comment -> R ()-spitCommentNow spn comment = do+--+-- Note that it records the placement as well as printing. Every path that+-- emits a comment has to go through this or 'spitCommentPending', or+-- "Ormolu.Comments.Invariants" will report the comment as dropped and+-- Ormolu will refuse to format the file.+spitCommentNow ::+  -- | The slot the comment is being rendered in+  CommentSlot ->+  RealSrcSpan ->+  Comment ->+  R ()+spitCommentNow slot spn comment = do+  recordCommentPlacement CommentPlacement {cpSpan = spn, cpSlot = slot}   sitcc     . sequence_     . NE.intersperse newline     . fmap txt     . unComment     $ comment-  setSpanMark (CommentSpan spn)+  setLastEmitted (LastEmittedComment spn) --- | Output a 'Comment' at the end of correct line or after it depending on--- 'CommentPosition'. Used for comments that may potentially follow on the--- same line as something we just rendered, but not immediately after it.-spitCommentPending :: CommentPosition -> RealSrcSpan -> Comment -> R ()-spitCommentPending position spn comment = do+-- | Output a 'Comment' at the end of the correct line, or after it,+-- depending on the 'CommentPosition'. Used for comments that may follow on+-- the same line as something we just rendered, but not immediately after it.+spitCommentPending ::+  -- | The slot the comment is being rendered in+  CommentSlot ->+  CommentPosition ->+  RealSrcSpan ->+  Comment ->+  R ()+spitCommentPending slot position spn comment = do+  recordCommentPlacement CommentPlacement {cpSpan = spn, cpSlot = slot}   let wrapper = case position of         OnTheSameLine -> sitcc         OnNextLine -> id@@ -281,4 +203,4 @@     . fmap (registerPendingCommentLine position)     . unComment     $ comment-  setSpanMark (CommentSpan spn)+  setLastEmitted (LastEmittedComment spn)
src/Ormolu/Printer/Internal.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} --- | In most cases import "Ormolu.Printer.Combinators" instead, these+-- | In most cases, import "Ormolu.Printer.Combinators" instead; these -- functions are the low-level building blocks and should not be used on -- their own. The 'R' monad is re-exported from "Ormolu.Printer.Combinators" -- as well.@@ -14,12 +14,12 @@     -- * Internal functions     txt,     txtStripIndent,-    interferingTxt,     atom,     space,     newline,     declNewline,     multilineCommentNewline,+    newlineLiteral,     askSourceType,     askModuleFixityMap,     askDebug,@@ -43,22 +43,27 @@     -- * Special helpers for comment placement     CommentPosition (..),     registerPendingCommentLine,-    trimSpanStream,-    nextEltSpan,-    popComment,-    getEnclosingComments,+    withAnchorMap,+    getCommentsAnchoredWithin,+    getCommentsBefore,     getEnclosingSpan,-    getEnclosingSpanWhere,     withEnclosingSpan,     thisLineSpans,      -- * Stateful markers-    SpanMark (..),-    spanMarkSpan,+    LastEmitted (..),+    lastEmittedSpan,+    setLastEmitted,+    getLastEmitted,++    -- * Haddocks     HaddockStyle (..),-    setSpanMark,-    getSpanMark,+    lookupHaddockText, +    -- * Recording comment placement+    recordCommentPlacement,+    recordVisitedSpan,+     -- * Extensions     isExtensionEnabled,   )@@ -70,11 +75,9 @@ import Data.Bool (bool) import Data.Char (isSpace) import Data.Choice (Choice)-import Data.Choice qualified as Choice-import Data.Coerce-import Data.Functor ((<&>)) import Data.Functor.Identity (runIdentity) import Data.List (find)+import Data.Map.Strict qualified as M import Data.Maybe (listToMaybe) import Data.Set (Set) import Data.Text (Text)@@ -87,17 +90,18 @@ import GHC.LanguageExtensions.Type import GHC.Types.SrcLoc import GHC.Utils.Outputable (Outputable)+import Ormolu.Comments.Anchor (AnchorMap, commentsAnchoredWithin, commentsBefore) import Ormolu.Config import Ormolu.Fixity (ModuleFixityMap) import Ormolu.Parser.CommentStream-import Ormolu.Printer.SpanStream+import Ormolu.Printer.CommentPlacement import Ormolu.Utils (showOutputable)  ---------------------------------------------------------------------------- -- The 'R' monad  -- | The 'R' monad hosts combinators that allow us to describe how to render--- AST.+-- the AST. newtype R a = R (ReaderT RC (State SC) a)   deriving (Functor, Applicative, Monad) @@ -109,7 +113,7 @@     rcIndent :: !Int,     -- | Current layout     rcLayout :: Layout,-    -- | Spans of enclosing elements of AST+    -- | Spans of enclosing elements of the AST     rcEnclosingSpans :: [RealSrcSpan],     -- | Whether the last expression in the layout can use braces     rcCanUseBraces :: Bool,@@ -122,7 +126,9 @@     -- | Module fixity map     rcModuleFixityMap :: ModuleFixityMap,     -- | Whether to print out debug information during printing-    rcDebug :: !Bool+    rcDebug :: !(Choice "debug"),+    -- | Source text of the module's Haddocks+    rcHaddockText :: HaddockText   }  -- | State context of 'R'.@@ -133,22 +139,26 @@     scIndent :: !Int,     -- | Rendered source code so far     scBuilder :: Builder,-    -- | Span stream-    scSpanStream :: SpanStream,     -- | Spans of atoms that have been printed on the current line so far     scThisLineSpans :: [RealSrcSpan],-    -- | Comment stream-    scCommentStream :: CommentStream,-    -- | Pending comment lines (in reverse order) to be inserted before next-    -- newline, 'Int' is the indentation level+    -- | Comments that have not been emitted yet, by the element they are+    -- attached to+    scAnchorMap :: AnchorMap,+    -- | Pending comment lines (in reverse order) to be inserted before the+    -- next newline     scPendingComments :: ![(CommentPosition, Text)],     -- | Whether to output a space before the next output     scRequestedDelimiter :: !RequestedDelimiter,-    -- | An auxiliary marker for keeping track of last output element-    scSpanMark :: !(Maybe SpanMark)+    -- | What was emitted last, used both for preserving blank lines from+    -- the input and for recognizing runs of comments+    scLastEmitted :: !LastEmitted,+    -- | Comment placement decisions made so far, in reverse order+    scCommentPlacements :: [CommentPlacement],+    -- | Spans of the elements the printer has entered, in reverse order+    scVisitedSpans :: [RealSrcSpan]   } --- | Make sure next output is delimited by one of the following.+-- | Make sure the next output is delimited by one of the following. data RequestedDelimiter   = -- | A space     RequestedSpace@@ -164,17 +174,17 @@  -- | 'Layout' options. data Layout-  = -- | Put everything on single line+  = -- | Put everything on a single line     SingleLine   | -- | Use multiple lines     MultiLine   deriving (Eq, Show) --- | Modes for rendering of pending comments.+-- | Modes for rendering pending comments. data CommentPosition   = -- | Put the comment on the same line     OnTheSameLine-  | -- | Put the comment on next line+  | -- | Put the comment on the next line     OnNextLine   deriving (Eq, Show) @@ -182,10 +192,8 @@ runR ::   -- | Monad to run   R () ->-  -- | Span stream-  SpanStream ->-  -- | Comment stream-  CommentStream ->+  -- | Comments, attached to the elements they belong to+  AnchorMap ->   PrinterOptsTotal ->   Set ModuleName ->   -- | Whether the source is a signature or a regular module@@ -194,12 +202,20 @@   EnumSet Extension ->   -- | Module fixity map   ModuleFixityMap ->-  -- | Resulting rendition-  Bool ->-  Text-runR (R m) sstream cstream printerOpts localModules sourceType extensions moduleFixityMap debug =-  TL.toStrict . toLazyText . scBuilder $ execState (runReaderT m rc) sc+  -- | Whether to print out debug information during printing+  Choice "debug" ->+  -- | Source text of the module's Haddocks+  HaddockText ->+  -- | The rendition, the comment placement decisions that were made along+  -- the way, and the spans of the elements that were entered+  (Text, [CommentPlacement], [RealSrcSpan])+runR (R m) anchorMap printerOpts localModules sourceType extensions moduleFixityMap debug haddockText =+  ( TL.toStrict . toLazyText . scBuilder $ finalSc,+    reverse (scCommentPlacements finalSc),+    reverse (scVisitedSpans finalSc)+  )   where+    finalSc = execState (runReaderT m rc) sc     rc =       RC         { rcIndent = 0,@@ -211,19 +227,21 @@           rcExtensions = extensions,           rcSourceType = sourceType,           rcModuleFixityMap = moduleFixityMap,-          rcDebug = debug+          rcDebug = debug,+          rcHaddockText = haddockText         }     sc =       SC         { scColumn = 0,           scIndent = 0,           scBuilder = mempty,-          scSpanStream = sstream,           scThisLineSpans = [],-          scCommentStream = cstream,+          scAnchorMap = anchorMap,           scPendingComments = [],           scRequestedDelimiter = VeryBeginning,-          scSpanMark = Nothing+          scLastEmitted = LastEmittedOther,+          scCommentPlacements = [],+          scVisitedSpans = []         }  ----------------------------------------------------------------------------@@ -234,12 +252,7 @@ data SpitType   = -- | Simple opaque text that breaks comment series.     SimpleText-  | -- | Like 'SimpleText', but assume that when this text is inserted it-    -- will separate an 'Atom' and its pending comments, so insert an extra-    -- 'newline' in that case to force the pending comments and continue on-    -- a fresh line.-    InterferingText-  | -- | An atom that typically have span information in the AST and can+  | -- | An atom that typically has span information in the AST and can     -- have comments attached to it.     Atom   | -- | Used for rendering comment lines.@@ -268,17 +281,9 @@   let (leadingSpaces, s') = T.span isSpace s   txt $ T.drop indent leadingSpaces <> s' --- | Similar to 'txt' but the text inserted this way is assumed to break the--- “link” between the preceding atom and its pending comments.-interferingTxt ::-  -- | 'Text' to output-  Text ->-  R ()-interferingTxt = spit InterferingText---- | Output 'Outputable' fragment of AST. This can be used to output numeric--- literals and similar. Everything that doesn't have inner structure but--- does have an 'Outputable' instance.+-- | Output an 'Outputable' fragment of the AST. This can be used to output+-- numeric literals and similar: anything that doesn't have inner structure+-- but does have an 'Outputable' instance. atom ::   (Outputable a) =>   a ->@@ -295,8 +300,6 @@ spit _ "" = return () spit stype text = do   requestedDel <- R (gets scRequestedDelimiter)-  pendingComments <- R (gets scPendingComments)-  when (stype == InterferingText && not (null pendingComments)) newline   case requestedDel of     RequestedNewline -> do       R . modify $ \sc ->@@ -333,12 +336,12 @@                     Just x -> x : xs                   _ -> xs,           scRequestedDelimiter = RequestedNothing,-          scSpanMark =+          scLastEmitted =             -- If there are pending comments, do not reset last comment             -- location.             if (stype == CommentPart) || (not . null . scPendingComments) sc-              then scSpanMark sc-              else Nothing+              then scLastEmitted sc+              else LastEmittedOther         }  -- | This primitive /does not/ necessarily output a space. It just ensures@@ -377,18 +380,25 @@         scRequestedDelimiter = AfterNewline       } --- | Output a newline. First time 'newline' is used after some non-'newline'--- output it gets inserted immediately. Second use of 'newline' does not--- output anything but makes sure that the next non-white space output will--- be prefixed by a newline. Using 'newline' more than twice in a row has no--- effect. Also, using 'newline' at the very beginning has no effect, this--- is to avoid leading whitespace.+-- | Output a newline. The first time 'newline' is used after some+-- non-'newline' output, it gets inserted immediately. The second use of+-- 'newline' does not output anything but makes sure that the next+-- non-whitespace output will be prefixed by a newline. Using 'newline' more+-- than twice in a row has no effect. Also, using 'newline' at the very+-- beginning has no effect; this is to avoid leading whitespace. -- -- Similarly to 'space', this design prevents trailing newlines and makes it -- hard to output more than one blank newline in a row. newline :: R () newline = do-  indent <- R (gets scIndent)+  lineIndent <- R (gets scIndent)+  logicalIndent <- R (asks rcIndent)+  -- A trailing comment block spills onto the lines below the code it+  -- trails. Those lines take the indentation of the line the block started+  -- on, unless the construct being printed is indented further, in which+  -- case they follow it: dropping to the start of the line would put the+  -- rest of a block comment outside the declaration it was written in.+  let indent = max lineIndent logicalIndent   cs <- reverse <$> R (gets scPendingComments)   case cs of     [] -> newlineRaw@@ -438,6 +448,19 @@             _ -> AfterNewline         } +-- | Insert a literal newline without modifying the internal state of the+-- printer. This is to be used in exceptional cases, e.g. for printing+-- multiline string literals.+newlineLiteral :: R ()+newlineLiteral = R . modify $ \sc ->+  sc+    { scBuilder = scBuilder sc <> "\n",+      scColumn = 0,+      scIndent = 0,+      scThisLineSpans = [],+      scRequestedDelimiter = AfterNewline+    }+ -- | Return the source type. askSourceType :: R SourceType askSourceType = R (asks rcSourceType)@@ -449,7 +472,7 @@ -- | Retrieve whether we should print out certain debug information while -- printing. askDebug :: R (Choice "debug")-askDebug = R (asks (Choice.fromBool . rcDebug))+askDebug = R (asks rcDebug)  -- | Like 'inci', but indents by exactly the given number of steps. inciBy :: Int -> R () -> R ()@@ -498,16 +521,16 @@   let step = truncate $ fromIntegral indentStep * x   inciBy step m --- | Increase indentation level by one indentation step for the inner--- computation. 'inci' should be used when a part of code must be more+-- | Increase the indentation level by one indentation step for the inner+-- computation. 'inci' should be used when a piece of code must be more -- indented relative to the parts outside of 'inci' in order for the output--- to be valid Haskell. When layout is single-line there is no obvious--- effect, but with multi-line layout correct indentation levels matter.+-- to be valid Haskell. With single-line layout there is no visible effect,+-- but with multi-line layout correct indentation levels matter. inci :: R () -> R () inci = inciByFrac 1 --- | Set indentation level for the inner computation equal to current--- column. This makes sure that the entire inner block is uniformly+-- | Set the indentation level for the inner computation equal to the+-- current column. This makes sure that the entire inner block is uniformly -- \"shifted\" to the right. sitcc :: R () -> R () sitcc (R m) = do@@ -528,7 +551,7 @@     Leading -> id x     Trailing -> sitcc x --- | Set 'Layout' for internal computation.+-- | Set the 'Layout' for the inner computation. enterLayout :: Layout -> R () -> R () enterLayout l (R m) = R (local modRC m)   where@@ -537,7 +560,7 @@         { rcLayout = l         } --- | Do one or another thing depending on current 'Layout'.+-- | Do one thing or another depending on the current 'Layout'. vlayout ::   -- | Single line   R a ->@@ -550,7 +573,7 @@     SingleLine -> sline     MultiLine -> mline --- | Get current 'Layout'.+-- | Get the current 'Layout'. getLayout :: R Layout getLayout = R (asks rcLayout) @@ -564,10 +587,10 @@ ---------------------------------------------------------------------------- -- Special helpers for comment placement --- | Register a comment line for outputting. It will be inserted right--- before next newline. When the comment goes after something else on the--- same line, a space will be inserted between preceding text and the--- comment when necessary.+-- | Register a comment line for output. It will be inserted right before+-- the next newline. When the comment goes after something else on the same+-- line, a space will be inserted between the preceding text and the comment+-- when necessary. registerPendingCommentLine ::   -- | Comment position   CommentPosition ->@@ -580,52 +603,34 @@       { scPendingComments = (position, text) : scPendingComments sc       } --- | Drop elements that begin before or at the same place as given--- 'SrcSpan'.-trimSpanStream ::-  -- | Reference span-  RealSrcSpan ->-  R ()-trimSpanStream ref = do-  let leRef :: RealSrcSpan -> Bool-      leRef x = realSrcSpanStart x <= realSrcSpanStart ref-  R . modify $ \sc ->-    sc-      { scSpanStream = coerce (dropWhile leRef) (scSpanStream sc)-      }---- | Get location of next element in AST.-nextEltSpan :: R (Maybe RealSrcSpan)-nextEltSpan = listToMaybe . coerce <$> R (gets scSpanStream)+-- | Claim comments from the anchor map, storing what is left.+withAnchorMap :: (AnchorMap -> (a, AnchorMap)) -> R a+withAnchorMap f = R . state $ \sc ->+  let (a, am) = f (scAnchorMap sc)+   in (a, sc {scAnchorMap = am}) --- | Pop a 'Comment' from the 'CommentStream' if given predicate is--- satisfied and there are comments in the stream.-popComment ::-  (LComment -> Bool) ->-  R (Maybe LComment)-popComment f = R $ do-  CommentStream cstream <- gets scCommentStream-  case cstream of-    (x : xs) | f x -> do-      modify $ \sc -> sc {scCommentStream = CommentStream xs}-      return $ Just x-    _ -> return Nothing+-- | Get the comments that will be printed before the element at the given+-- span. Like 'getCommentsAnchoredWithin', this only looks; it does not+-- claim.+getCommentsBefore :: RealSrcSpan -> R [LComment]+getCommentsBefore spn = withAnchorMap (\am -> (commentsBefore spn am, am)) --- | Get the comments contained in the enclosing span.-getEnclosingComments :: R [LComment]-getEnclosingComments = do-  isEnclosed <--    getEnclosingSpan <&> \case-      Just enclSpan -> containsSpan enclSpan-      Nothing -> const False-  CommentStream cstream <- R $ gets scCommentStream-  pure $ takeWhile (isEnclosed . getLoc) cstream+-- | Get the comments attached to the element at the given span, or to+-- anything inside it.+--+-- This only looks; it does not claim. The layout decisions that ask this+-- run before the comments are emitted, and claiming here would leave+-- nothing for the printer to emit later.+getCommentsAnchoredWithin :: RealSrcSpan -> R [LComment]+getCommentsAnchoredWithin region =+  withAnchorMap (\am -> (commentsAnchoredWithin region am, am))  -- | Get the immediately enclosing 'RealSrcSpan'. getEnclosingSpan :: R (Maybe RealSrcSpan) getEnclosingSpan = getEnclosingSpanWhere (const True) --- | Get the first enclosing 'RealSrcSpan' that satisfies given predicate.+-- | Get the first enclosing 'RealSrcSpan' that satisfies the given+-- predicate. getEnclosingSpanWhere ::   -- | Predicate to use   (RealSrcSpan -> Bool) ->@@ -633,7 +638,7 @@ getEnclosingSpanWhere f =   find f <$> R (asks rcEnclosingSpans) --- | Set 'RealSrcSpan' of enclosing span for the given computation.+-- | Set the 'RealSrcSpan' of the enclosing span for the given computation. withEnclosingSpan :: RealSrcSpan -> R () -> R () withEnclosingSpan spn (R m) = R (local modRC m)   where@@ -649,23 +654,44 @@ ---------------------------------------------------------------------------- -- Stateful markers --- | An auxiliary marker for keeping track of last output element.-data SpanMark-  = -- | Haddock comment-    HaddockSpan HaddockStyle RealSrcSpan-  | -- | Non-haddock comment-    CommentSpan RealSrcSpan-  | -- | A statement in a do-block and such span-    StatementSpan RealSrcSpan+-- | What the printer emitted last, and where it came from in the input.+--+-- This is about spacing, not about attachment: it is what lets a blank line+-- in the input be preserved in the output, and what lets a run of comment+-- lines be recognized as one. Statements are tracked for the first of those+-- reasons, Haddocks for the second.+data LastEmitted+  = -- | Nothing yet, or ordinary code+    LastEmittedOther+  | -- | A comment occupying the given span of the input+    LastEmittedComment RealSrcSpan+  | -- | A Haddock occupying the given span of the input+    LastEmittedHaddock RealSrcSpan+  | -- | A statement of a layout block occupying the given span+    LastEmittedStatement RealSrcSpan+  deriving (Eq, Show) --- | Project 'RealSrcSpan' from 'SpanMark'.-spanMarkSpan :: SpanMark -> RealSrcSpan-spanMarkSpan = \case-  HaddockSpan _ s -> s-  CommentSpan s -> s-  StatementSpan s -> s+-- | Where the last emitted thing came from in the input, if it came from+-- anywhere in particular.+lastEmittedSpan :: LastEmitted -> Maybe RealSrcSpan+lastEmittedSpan = \case+  LastEmittedOther -> Nothing+  LastEmittedComment s -> Just s+  LastEmittedHaddock s -> Just s+  LastEmittedStatement s -> Just s --- | Haddock string style.+-- | Record what was emitted last.+setLastEmitted :: LastEmitted -> R ()+setLastEmitted lastEmitted = R . modify $ \sc ->+  sc+    { scLastEmitted = lastEmitted+    }++-- | Report what was emitted last.+getLastEmitted :: R LastEmitted+getLastEmitted = R (gets scLastEmitted)++-- | Haddock string style, i.e. the trigger a Haddock is rendered with. data HaddockStyle   = -- | @-- |@     Pipe@@ -676,19 +702,38 @@   | -- | @-- $@     Named String --- | Set span of last output comment.-setSpanMark ::-  -- | Span mark to set-  SpanMark ->-  R ()-setSpanMark spnMark = R . modify $ \sc ->+-- | The source text of the Haddock at the given span, if it is one of the+-- module's Haddocks. See 'Ormolu.Parser.CommentStream.HaddockText'.+lookupHaddockText :: RealSrcSpan -> R (Maybe Comment)+lookupHaddockText spn = R (asks (M.lookup spn . rcHaddockText))++----------------------------------------------------------------------------+-- Recording comment placement++-- | Record the fact that a comment was rendered in a particular slot.+--+-- Every code path that emits a comment has to call this. What is recorded+-- here is what "Ormolu.Comments.Invariants" checks the input's comments+-- against, so a comment emitted without being recorded is reported as+-- dropped and Ormolu refuses to format the file.+recordCommentPlacement :: CommentPlacement -> R ()+recordCommentPlacement placement = R . modify $ \sc ->   sc-    { scSpanMark = Just spnMark+    { scCommentPlacements = placement : scCommentPlacements sc     } --- | Get span of last output comment.-getSpanMark :: R (Maybe SpanMark)-getSpanMark = R (gets scSpanMark)+-- | Record that the printer entered the element with the given span.+--+-- Not every span in the AST is entered: the printer renders plenty of+-- syntax with 'txt' rather than through 'Ormolu.Printer.Combinators.located',+-- so a @where@ clause, for instance, has a span but is never entered. A+-- comment can only be attached to an element that is entered, because+-- entering it is the only moment at which the comment could be emitted.+recordVisitedSpan :: RealSrcSpan -> R ()+recordVisitedSpan spn = R . modify $ \sc ->+  sc+    { scVisitedSpans = spn : scVisitedSpans sc+    }  ---------------------------------------------------------------------------- -- Helpers for braces
src/Ormolu/Printer/Meat/Common.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}  -- | Rendering of commonly useful bits.@@ -12,32 +14,45 @@     p_qualName,     p_infixDefHelper,     p_hsDoc,-    p_hsDoc',+    p_hsDocInline,+    p_hsDocWith,+    multiLineIfDocumented,+    switchLayoutDocumented,+    hasLineHaddocks,+    p_hsDocName,     p_sourceText,     p_namespaceSpec,+    p_hsMultAnn,     p_arrow,+    getIsFourmoluMultiHaddockPrintStyle,   ) where  import Control.Monad-import Data.Choice (Choice)+import Data.Choice (Choice, pattern Is, pattern Isn't) import Data.Choice qualified as Choice-import Data.Foldable (traverse_)+import Data.Data (Data)+import Data.Generics.Schemes (listify)+import Data.List.NonEmpty qualified as NE+import Data.Maybe (isJust)+import Data.Text (Text) import Data.Text qualified as T import GHC.Data.FastString+import GHC.Hs (ConDecl (..), LConDecl) import GHC.Hs.Binds import GHC.Hs.Doc import GHC.Hs.Extension (GhcPs) import GHC.Hs.ImpExp+import GHC.Hs.Type import GHC.LanguageExtensions.Type (Extension (..)) import GHC.Parser.Annotation import GHC.Types.Name.Occurrence (OccName (..), occNameString) import GHC.Types.Name.Reader import GHC.Types.SourceText import GHC.Types.SrcLoc-import Language.Haskell.Syntax (HsArrowOf (..)) import Language.Haskell.Syntax.Module.Name import Ormolu.Config+import Ormolu.Parser.CommentStream (Comment, isMultilineComment, unComment) import Ormolu.Printer.Combinators import Ormolu.Utils @@ -48,7 +63,8 @@   | -- | Top-level declarations     Free --- | Outputs the name of the module-like entity, preceeded by the correct prefix ("module" or "signature").+-- | Output the name of the module-like entity, preceded by the correct+-- prefix (@module@ or @signature@). p_hsmodName :: ModuleName -> R () p_hsmodName mname = do   sourceType <- askSourceType@@ -73,6 +89,10 @@     txt "type"     space     p_rdrName x+  IEData _ x -> do+    txt "data"+    space+    p_rdrName x  -- | Render a @'LocatedN' 'RdrName'@. p_rdrName :: LocatedN RdrName -> R ()@@ -87,13 +107,18 @@         NameAnnRArrow {nann_mopen = Just _} -> parens N         -- special case for unboxed unit tuples         NameAnnOnly {nann_adornment = NameParensHash {}} -> const $ txt "(# #)"+        -- An empty list reaches the printer as a name, not as a list, so+        -- this is the only place a comment written between its brackets can+        -- be given something to attach to.+        NameAnnOnly {nann_adornment = NameSquare open _} ->+          const $ brackets N (locatedEmpty (getEpTokenSrcSpan open))         _ -> id        -- When UnboxedSums is enabled, `(#` is a single lexeme, so we have to       -- insert spaces when we have a parenthesized operator starting with `#`.       handleUnboxedSumsAndHashInteraction         | unboxedSums,-          -- Qualified names do not start wth a `#`.+          -- Qualified names do not start with a `#`.           Unqual (occNameString -> '#' : _) <- x =             \y -> space *> y <* space         | otherwise = id@@ -106,7 +131,7 @@     Orig _ occName ->       -- This is used when GHC generates code that will be fed into       -- the renamer (e.g. from deriving clauses), but where we want-      -- to say that something comes from given module which is not+      -- to say that something comes from a given module that is not       -- specified in the source code, e.g. @Prelude.map@.       --       -- My current understanding is that the provided module name@@ -123,19 +148,20 @@   txt "."   atom occName --- | A helper for formatting infix constructions in lhs of definitions.+-- | A helper for formatting infix constructions on the left-hand side of+-- definitions. p_infixDefHelper ::   -- | Whether to format in infix style-  Bool ->+  Choice "infixStyle" ->   -- | Whether to bump indentation for arguments-  Bool ->+  Choice "indentArgs" ->   -- | How to print the operator\/name   R () ->   -- | How to print the arguments   [R ()] ->   R () p_infixDefHelper isInfix indentArgs name args =-  case (isInfix, args) of+  case (Choice.toBool isInfix, args) of     (True, p0 : p1 : ps) -> do       let parens' =             if null ps@@ -148,16 +174,22 @@           name           space           p1-      unless (null ps) . inciIf indentArgs $ do+      unless (null ps) . inciIf (Choice.toBool indentArgs) $ do         breakpoint         sitcc (sep breakpoint sitcc ps)     (_, ps) -> do       name       unless (null ps) $ do         breakpoint-        inciIf indentArgs $ sitcc (sep breakpoint sitcc args)+        inciIf (Choice.toBool indentArgs) $+          sitcc (sep breakpoint sitcc args)  -- | Print a Haddock.+--+-- The author's own text is reused whenever it can be, so a @{- | … -}@+-- comes back as a block comment and an empty @-- |@ survives; see+-- 'haddockAsWritten' for when it cannot be. Otherwise the Haddock is+-- rebuilt from its 'HsDocString'. p_hsDoc ::   -- | Haddock style   HaddockStyle ->@@ -168,69 +200,168 @@   R () p_hsDoc hstyle needsNewline lstr = do   poHStyle <- getPrinterOpt poHaddockStyle-  p_hsDoc' poHStyle hstyle needsNewline lstr+  p_hsDocWith poHStyle hstyle needsNewline (Isn't #mayShareLine) lstr --- | Print a Haddock.-p_hsDoc' ::+-- | 'p_hsDoc' for a Haddock inside a construct that may legitimately be laid+-- out on one line.+--+-- A Haddock that comes back out as @{- | … -}@ is self-delimiting, so it+-- ends with a 'breakpoint' rather than a newline and can share the line:+-- @data A = A {- | a number -} Int Bool@ stays as written. One rendered as+-- @--@ lines still ends the line, since it owns the rest of it.+p_hsDocInline :: HaddockStyle -> Choice "endNewline" -> LHsDoc GhcPs -> R ()+p_hsDocInline hstyle needsNewline lstr = do+  poHStyle <- getPrinterOpt poHaddockStyle+  p_hsDocWith poHStyle hstyle needsNewline (Is #mayShareLine) lstr++-- | The worker behind 'p_hsDoc' and 'p_hsDocInline'.+p_hsDocWith ::   -- | 'haddock-style' configuration option   HaddockPrintStyle ->-  -- | Haddock style   HaddockStyle ->-  -- | Finish the doc string with a newline   Choice "endNewline" ->-  -- | The 'LHsDoc' to render+  Choice "mayShareLine" ->   LHsDoc GhcPs ->   R ()-p_hsDoc' poHStyle hstyle needsNewline (L l str) = do-  let isCommentSpan = \case-        HaddockSpan _ _ -> True-        CommentSpan _ -> True+p_hsDocWith poHStyle hstyle needsNewline mayShareLine ldoc = do+  let goesAfterCommentOrHaddock = \case+        LastEmittedHaddock _ -> True+        LastEmittedComment _ -> True         _ -> False-  goesAfterComment <- maybe False isCommentSpan <$> getSpanMark+  goesAfterComment <- goesAfterCommentOrHaddock <$> getLastEmitted   -- Make sure the Haddock is separated by a newline from other comments.   when goesAfterComment newline--  let shouldEscapeCommentBraces =-        case poHStyle of-          HaddockSingleLine -> False-          HaddockMultiLine -> True-          HaddockMultiLineCompact -> True-  let docStringLines = splitDocString shouldEscapeCommentBraces $ hsDocString str--  if poHStyle == HaddockSingleLine || length docStringLines <= 1-    then do+  poHStyle' <- resolveHaddockPrintStyle poHStyle hstyle ldoc+  case poHStyle' of+    HaddockPrint_AsWritten written -> do+      let lns = unComment written+      sitcc . sequence_ . NE.intersperse newline . fmap txt $ lns+    HaddockPrint_Single -> do       txt $ "-- " <> haddockDelim       space       sep (newline >> txt "--" >> space) txt docStringLines-    else do-      txt . T.concat $-        [ "{-",-          case (hstyle, poHStyle) of-            (Pipe, HaddockMultiLineCompact) -> ""-            _ -> " ",-          haddockDelim-        ]+    HaddockPrint_Multi delimSpace -> do+      txt $ "{-" <> delimSpace <> haddockDelim       space       sep multilineCommentNewline txtStripIndent docStringLines       newline       txt "-}"--  when (Choice.isTrue needsNewline) newline-  traverse_ (setSpanMark . HaddockSpan hstyle) =<< getSrcSpan l+  -- A Haddock rendered as @--@ lines owns the rest of its line and has to+  -- end it. One rendered as @{- | … -}@ is self-delimiting, so a space will+  -- do when the surrounding layout is single-line.+  when (Choice.isTrue needsNewline) $+    if Choice.isTrue mayShareLine && isMultilineHaddockPrintStyle poHStyle'+      then breakpoint+      else newline+  case getLoc ldoc of+    UnhelpfulSpan _ ->+      -- It's often the case that the comment itself doesn't have a span+      -- attached to it, and instead its location can be obtained from the+      -- nearest enclosing span.+      getEnclosingSpan >>= mapM_ (setLastEmitted . LastEmittedHaddock)+    RealSrcSpan spn _ -> setLastEmitted (LastEmittedHaddock spn)   where+    docStringLines = getDocStringLines ldoc     haddockDelim =       case hstyle of         Pipe -> "|"         Caret -> "^"         Asterisk n -> T.replicate n "*"         Named name -> "$" <> T.pack name-    getSrcSpan = \case-      -- It's often the case that the comment itself doesn't have a span-      -- attached to it and instead its location can be obtained from-      -- nearest enclosing span.-      UnhelpfulSpan _ -> getEnclosingSpan-      RealSrcSpan spn _ -> pure $ Just spn +-- | Lay the computation out on several lines if rendering the given+-- fragment of the syntax tree will emit a Haddock as @--@ lines.+--+-- Such a Haddock takes whole lines: emitted inside a bracketed construct+-- that was put on one line, it swallows the rest of that line, closing+-- bracket and all. The author writes it in front of the construct, so its+-- span is outside the construct's and 'switchLayout' cannot see it; what+-- decides is where it will be /printed/, which is inside. Hence+-- @data A = A deriving (Eq)@ documented on the @Eq@ came out as+-- @deriving (-- \| B@, and a documented field of a one-line record as+-- @{-- \| …@, which does not parse at all.+--+-- A Haddock that comes back out as @{- | … -}@ is self-delimiting and does+-- not force anything, so @data A = A {- | a number -} Int Bool@ is left+-- alone rather than being exploded over five lines.+multiLineIfDocumented :: (Data a) => a -> R () -> R ()+multiLineIfDocumented x m = do+  breaks <- hasLineHaddocks x+  if breaks then enterLayout MultiLine m else m++-- | 'switchLayout', except that the layout is multi-line regardless of the+-- spans when rendering the given fragment will emit a Haddock as @--@+-- lines.+--+-- Use this rather than 'multiLineIfDocumented' around a 'switchLayout': the+-- override has to be applied after the spans have had their say, or it is+-- immediately discarded.+switchLayoutDocumented ::+  (Data a) =>+  -- | Fragment that decides whether documentation will be printed+  a ->+  -- | Span that controls layout otherwise+  [SrcSpan] ->+  -- | Computation to run with changed layout+  R () ->+  R ()+switchLayoutDocumented x spans' =+  switchLayout spans' . multiLineIfDocumented x++-- | Does rendering this fragment emit a Haddock as @--@ lines?+--+-- Every site this is asked about prints its Haddocks in 'Pipe' style, which+-- is what decides whether the author's own text can be reused.+hasLineHaddocks :: (Data a) => a -> R Bool+hasLineHaddocks x = case listify (const True :: LHsDoc GhcPs -> Bool) x of+  -- A doc string that is not reachable as an 'LHsDoc' cannot be inspected,+  -- so assume the worst and break.+  [] -> pure (containsHaddocks x)+  docs -> or <$> traverse rendersAsLines docs+  where+    rendersAsLines doc = do+      poHStyle <- getPrinterOpt poHaddockStyle+      not . isMultilineHaddockPrintStyle <$> resolveHaddockPrintStyle poHStyle Pipe doc++-- | The author's own text for a Haddock, when it can be reused.+--+-- 'Nothing' means the Haddock has to be rebuilt from its 'HsDocString' as+-- @--@ lines: either its text was not kept, or it is about to be rendered+-- in a different style than it was written in. Ormolu moves a trailing+-- @-- ^ X@ in front of what it documents and writes it as @-- | X@, and+-- keeping the author's text there would leave a @^@ pointing at the wrong+-- thing.+haddockAsWritten :: HaddockStyle -> LHsDoc GhcPs -> R (Maybe Comment)+haddockAsWritten hstyle (L l _) = do+  asWritten <- maybe (pure Nothing) lookupHaddockText (srcSpanToRealSrcSpan l)+  pure (mfilter (writtenAs hstyle . NE.head . unComment) asWritten)++-- | Was the Haddock written in the style it is about to be rendered in?+writtenAs :: HaddockStyle -> Text -> Bool+writtenAs hstyle firstLine =+  case T.stripPrefix "--" opener of+    Just rest -> hasTrigger (T.stripStart rest)+    Nothing -> maybe False (hasTrigger . T.stripStart) (T.stripPrefix "{-" opener)+  where+    opener = T.stripStart firstLine+    hasTrigger t = case hstyle of+      Pipe -> "|" `T.isPrefixOf` t+      Caret -> "^" `T.isPrefixOf` t+      Asterisk n ->+        T.replicate n "*" `T.isPrefixOf` t+          && not (T.replicate (n + 1) "*" `T.isPrefixOf` t)+      Named name -> ("$" <> T.pack name) `T.isPrefixOf` t++-- | Print the anchor of a named doc section. Unlike 'p_hsDoc' this is a+-- bare anchor with no doc string attached, so there is no span to report.+p_hsDocName :: String -> R ()+p_hsDocName name = do+  txt (hsDocNameText name)++-- | Render the anchor of a named doc section.+hsDocNameText :: String -> Text+hsDocNameText name = "-- $" <> T.pack name+ p_sourceText :: SourceText -> R () p_sourceText = \case   NoSourceText -> pure ()@@ -242,12 +373,74 @@   TypeNamespaceSpecifier _ -> txt "type" *> space   DataNamespaceSpecifier _ -> txt "data" *> space -p_arrow :: (mult -> R ()) -> HsArrowOf mult GhcPs -> R ()+p_hsMultAnn :: (mult -> R ()) -> HsMultAnnOf mult GhcPs -> R ()+p_hsMultAnn p_mult = \case+  HsUnannotated _ -> pure ()+  HsLinearAnn _ -> txt "%1"+  HsExplicitMult _ mult -> txt "%" *> p_mult mult++-- | Like 'p_hsMultAnn', except specifically for arrows, taking -XUnicodeSyntax+--   into account.+p_arrow :: (mult -> R ()) -> HsMultAnnOf mult GhcPs -> R () p_arrow p_mult = \case-  HsUnrestrictedArrow _ -> token'rarrow-  HsLinearArrow _ -> token'lolly+  HsUnannotated _ -> token'rarrow+  HsLinearAnn _ -> token'lolly   HsExplicitMult _ mult -> do     txt "%"     p_mult mult     space     token'rarrow++{----- Fourmolu: HaddockPrintStyle -----}++data HaddockPrintStyleResolved+  = HaddockPrint_AsWritten Comment+  | HaddockPrint_Single+  | HaddockPrint_Multi Text++resolveHaddockPrintStyle :: HaddockPrintStyle -> HaddockStyle -> LHsDoc GhcPs -> R HaddockPrintStyleResolved+resolveHaddockPrintStyle poHStyle hstyle ldoc =+  case poHStyle of+    HaddockSingleLine -> pure HaddockPrint_Single+    HaddockMultiLine -> resolveMulti " "+    HaddockMultiLineCompact -> resolveMulti $ case hstyle of Pipe -> ""; _ -> " "+    HaddockAuto -> do+      -- Print what the author wrote when we still have it. Rebuilding the+      -- comment from the doc string cannot preserve a @{- | … -}@ or an empty+      -- @-- |@, and what it loses it loses from the AST too.+      --+      -- If we can't figure out what the author wrote, fallback to single-line haddocks+      asWritten <- haddockAsWritten hstyle ldoc+      pure $ maybe HaddockPrint_Single HaddockPrint_AsWritten asWritten+  where+    resolveMulti delimSpace =+      pure $+        if length (getDocStringLines ldoc) <= 1+          then HaddockPrint_Single+          else HaddockPrint_Multi delimSpace++isMultilineHaddockPrintStyle :: HaddockPrintStyleResolved -> Bool+isMultilineHaddockPrintStyle = \case+  HaddockPrint_AsWritten written -> isMultilineComment written+  HaddockPrint_Single -> False+  HaddockPrint_Multi _ -> True++getDocStringLines :: LHsDoc GhcPs -> [Text]+getDocStringLines = splitDocString . hsDocString . unLoc++-- Hack to workaround upstream bug+-- https://github.com/tweag/ormolu/issues/1225+getIsFourmoluMultiHaddockPrintStyle :: LConDecl GhcPs -> R Bool+getIsFourmoluMultiHaddockPrintStyle (L _ decl) = do+  poHStyle <- getPrinterOpt poHaddockStyle+  pure $ isJust doc && isMulti poHStyle+  where+    doc =+      case decl of+        ConDeclGADT {con_doc} -> con_doc+        ConDeclH98 {con_doc} -> con_doc+    isMulti = \case+      HaddockSingleLine -> False+      HaddockMultiLine -> True+      HaddockMultiLineCompact -> True+      HaddockAuto -> False
src/Ormolu/Printer/Meat/Declaration.hs view
@@ -49,11 +49,12 @@ p_hsDecls :: FamilyStyle -> [LHsDecl GhcPs] -> R () p_hsDecls = p_hsDecls' Disregard --- | Like 'p_hsDecls' but respects user choices regarding grouping. If the+-- | Like 'p_hsDecls', but respects user choices regarding grouping. If the -- user omits newlines between declarations, we also omit them in most--- cases, except when said declarations have associated Haddocks.+-- cases, except when the declarations in question have associated Haddocks. ----- Does some normalization (compress subsequent newlines into a single one)+-- Does some normalization (compresses consecutive newlines into a single+-- one). p_hsDeclsRespectGrouping :: FamilyStyle -> [LHsDecl GhcPs] -> R () p_hsDeclsRespectGrouping = p_hsDecls' Respect @@ -70,7 +71,7 @@     renderGroup = NE.toList . fmap (located' $ dontUseBraces . p_hsDecl style)     renderGroupWithPrev prev curr =       -- We can omit a blank line when the user didn't add one, but we must-      -- ensure we always add blank lines around documented declarations+      -- ensure we always add blank lines around documented declarations.       case grouping of         Disregard ->           declNewline : renderGroup curr@@ -98,8 +99,8 @@   [NonEmpty (LHsDecl GhcPs)] groupDecls _ [] = [] groupDecls isSig (l@(L _ DocNext) : xs) =-  -- If the first element is a doc string for next element, just include it-  -- in the next block:+  -- If the first element is a doc string for the next element, just include+  -- it in the next block:   case groupDecls isSig xs of     [] -> [l :| []]     (x : xs') -> (l <| x) : xs'@@ -165,7 +166,7 @@   TyFamInstD _ x -> p_tyFamInstDecl style x   DataFamInstD _ x -> p_dataFamInstDecl style x --- | Determine if these declarations should be grouped together.+-- | Determine whether these declarations should be grouped together. groupedDecls ::   LHsDecl GhcPs ->   LHsDecl GhcPs ->@@ -193,14 +194,15 @@     (KindSignature n, ClassDeclaration n') -> n == n'     (KindSignature n, FamilyDeclaration n') -> n == n'     (KindSignature n, TypeSynonym n') -> n == n'-    -- Special case for TH splices, we look at locations+    -- Special case for TH splices: we look at locations.     (Splice, Splice) -> not (separatedByBlank id l_x l_y)-    -- This looks only at Haddocks, normal comments are handled elsewhere+    -- This looks only at Haddocks; normal comments are handled elsewhere.     (DocNext, _) -> True     (_, DocPrev) -> True     _ -> False --- | Detect declaration series that should not have blanks between them.+-- | Detect a series of declarations that should not have blanks between+-- them. declSeries ::   LHsDecl GhcPs ->   LHsDecl GhcPs ->@@ -235,12 +237,12 @@   WarningPragma n -> Just n   _ -> Nothing --- Declarations that do not refer to names+-- Declarations that do not refer to names.  pattern Splice :: HsDecl GhcPs pattern Splice <- SpliceD _ (SpliceDecl _ _ _) --- Declarations referring to a single name+-- Declarations referring to a single name.  pattern   InlinePragma,@@ -256,7 +258,7 @@   TypeSynonym ::     RdrName -> HsDecl GhcPs pattern InlinePragma n <- SigD _ (InlineSig _ (L _ n) _)-pattern SpecializePragma n <- SigD _ (SpecSig _ (L _ n) _ _)+pattern SpecializePragma n <- SigD _ (isSpecSig -> Just n) pattern SCCPragma n <- SigD _ (SCCFunSig _ (L _ n) _) pattern AnnTypePragma n <- AnnD _ (HsAnnotation _ (TypeAnnProvenance (L _ n)) _) pattern AnnValuePragma n <- AnnD _ (HsAnnotation _ (ValueAnnProvenance (L _ n)) _)@@ -267,7 +269,13 @@ pattern FamilyDeclaration n <- TyClD _ (FamDecl _ (FamilyDecl _ _ _ (L _ n) _ _ _ _)) pattern TypeSynonym n <- TyClD _ (SynDecl _ (L _ n) _ _ _) --- Declarations which can refer to multiple names+isSpecSig :: Sig GhcPs -> Maybe RdrName+isSpecSig = \case+  SpecSig _ (L _ n) _ _ -> Just n+  SpecSigE _ _ (deconstructExprFromSpecSigE -> (L _ n, _, _)) _ -> Just n+  _ -> Nothing++-- Declarations that can refer to multiple names.  pattern   TypeSignature,
src/Ormolu/Printer/Meat/Declaration/Class.hs view
@@ -1,5 +1,8 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-}  -- | Rendering of type class declarations.@@ -10,6 +13,8 @@  import Control.Arrow import Control.Monad+import Data.Choice (pattern Is)+import Data.Choice qualified as Choice import Data.Foldable import Data.Function (on) import Data.List (sortBy)@@ -61,8 +66,8 @@       for_ ctx p_classContext       switchLayout signatureSpans $         p_infixDefHelper-          (isInfix fixity)-          True+          (Choice.fromBool (isInfix fixity))+          (Is #indentArgs)           (p_rdrName name)           (located' p_hsTyVarBndr <$> hsq_explicit)       inci (p_classFundeps fdeps)@@ -70,7 +75,7 @@         breakpoint         txt "where"   unless (null allDecls) $ do-    breakpoint -- Ensure whitespace is added after where clause.+    breakpoint -- Ensure whitespace is added after the where clause.     inci (p_hsDeclsRespectGrouping Associated allDecls)  p_classContext :: LHsContext GhcPs -> R ()
src/Ormolu/Printer/Meat/Declaration/Data.hs view
@@ -3,11 +3,14 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-} --- | Renedring of data type declarations.+-- | Rendering of data type declarations. module Ormolu.Printer.Meat.Declaration.Data   ( p_dataDecl,   )@@ -19,9 +22,8 @@ import Data.List (sortOn) import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE-import Data.Maybe (isJust, isNothing, maybeToList)+import Data.Maybe (isJust, isNothing, mapMaybe, maybeToList) import Data.Text qualified as Text-import Data.Void import GHC.Hs import GHC.Types.Fixity import GHC.Types.ForeignCall@@ -79,8 +81,8 @@     forM_ dd_ctxt p_lhsContext     switchLayout constructorSpans $       p_infixDefHelper-        (isInfix fixity)-        True+        (Choice.fromBool (isInfix fixity))+        (Is #indentArgs)         (p_rdrName name)         (p_tyVar <$> tyVars)     forM_ dd_kindSig $ \k -> do@@ -114,21 +116,26 @@               conDeclConsSpans = \case                 ConDeclGADT {..} -> getLocA <$> con_names                 ConDeclH98 {..} -> getLocA con_name :| []-          if hasHaddocks dd_cons'+          -- A constructor documented with @--@ lines cannot share a line+          -- with anything. One documented with @{- | … -}@ can, so it is+          -- laid out as though it were undocumented.+          lineHaddocks <- consHaveLineHaddocks dd_cons'+          if lineHaddocks             then newline             else               if Choice.isTrue singleRecCon && compactLayoutAroundEquals                 then space                 else breakpoint-          equals+          txt "="           space           layout <- getLayout+          fourmoluSitccFix <- getIsFourmoluMultiHaddockPrintStyle first_dd_cons           let s =-                if layout == MultiLine || hasHaddocks dd_cons'+                if layout == MultiLine || lineHaddocks                   then newline >> txt "|" >> space                   else space >> txt "|" >> space               sitcc' =-                if hasHaddocks dd_cons' || Choice.isFalse singleRecCon+                if lineHaddocks || Choice.isFalse singleRecCon || fourmoluSitccFix                   then sitcc                   else id           sep s (sitcc' . located' (p_conDecl singleRecCon)) dd_cons'@@ -154,85 +161,69 @@   deriving (Eq, Ord)  p_conDecl :: Choice "singleRecCon" -> ConDecl GhcPs -> R ()-p_conDecl _ ConDeclGADT {..} = do+p_conDecl _ decl@ConDeclGADT {..} = do   mapM_ (p_hsDoc Pipe (With #endNewline)) con_doc-  switchLayout conDeclSpn $ do+  switchLayoutDocumented documented conDeclSpn $ do     let c :| cs = con_names     p_rdrName c     unless (null cs) . inci $ do       commaDel       sep commaDel p_rdrName cs-    inci $ do-      let conTy = case con_g_args of-            PrefixConGADT NoExtField xs ->-              let go (HsScaled a b) t = addCLocA t b (HsFunTy NoExtField a b t)-               in foldr go con_res_ty xs-            RecConGADT _ r ->-              addCLocA r con_res_ty $-                HsFunTy-                  NoExtField-                  (HsUnrestrictedArrow noAnn)-                  (la2la $ HsRecTy noAnn <$> r)-                  con_res_ty-          qualTy = case con_mb_cxt of-            Nothing -> conTy-            Just qs ->-              addCLocA qs conTy $-                HsQualTy NoExtField qs conTy-          quantifiedTy :: LHsType GhcPs-          quantifiedTy =-            addCLocA con_bndrs qualTy $-              hsOuterTyVarBndrsToHsType (unLoc con_bndrs) qualTy-      p_hsTypeAnnotation quantifiedTy+    inci $ p_hsFun decl   where+    -- Every part of the signature shares one layout decision, so any+    -- Haddock in any of them puts the whole of it on several lines.+    documented = (con_g_args, con_res_ty)+     conDeclSpn =-      fmap getLocA (NE.toList con_names)-        <> [getLocA con_bndrs]+      fmap getLocA (NE.toList con_names) <> conSigSpans+    conSigSpans =+      [getLocA con_outer_bndrs]         <> maybeToList (fmap getLocA con_mb_cxt)-        <> conArgsSpans-    conArgsSpans = case con_g_args of-      PrefixConGADT NoExtField xs -> getLocA . hsScaledThing <$> xs-      RecConGADT _ x -> [getLocA x]+        <> conArgResSpans+    conArgResSpans =+      getLocA con_res_ty : case con_g_args of+        PrefixConGADT NoExtField xs -> getLocA . cdf_type <$> xs+        RecConGADT _ x -> [getLocA x] p_conDecl singleRecCon ConDeclH98 {..} =   case con_args of-    PrefixCon (_ :: [Void]) xs -> do+    PrefixCon xs -> do       renderConDoc       renderContext-      switchLayout conDeclSpn $ do+      switchLayoutDocumented xs conDeclSpn $ do         p_rdrName con_name-        let args = hsScaledThing <$> xs-            argsHaveDocs = conArgsHaveHaddocks args-            delimiter = if argsHaveDocs then newline else breakpoint-        unless (null xs) delimiter+        unless (null xs) breakpoint         inci . sitcc $-          sep delimiter (sitcc . located' p_hsType) args+          sep breakpoint (sitcc . p_hsConDeclFieldWithDoc) xs     RecCon l -> do       renderConDoc       renderContext       switchLayout conDeclSpn $ do         p_rdrName con_name-        breakpoint-        inciIf (Choice.isFalse singleRecCon) (located l p_conDeclFields)-    InfixCon (HsScaled _ l) (HsScaled _ r) -> do-      -- manually render these-      let (lType, larg_doc) = splitDocTy l-      let (rType, rarg_doc) = splitDocTy r+        recordStyle <- getPrinterOpt poRecordStyle+        if recordStyle == RecordStyleKnr then space else breakpoint+        inciIf (Choice.isFalse singleRecCon) (located l p_hsConDeclRecFields)+    InfixCon l r -> do+      -- Render these manually.+      let larg_doc = cdf_doc l+          rarg_doc = cdf_doc r -      -- the constructor haddock can go on top of the entire constructor-      -- only if neither argument has haddocks+      -- The constructor Haddock can go on top of the entire constructor+      -- only if neither argument has Haddocks.       let putConDocOnTop = isNothing larg_doc && isNothing rarg_doc        when putConDocOnTop renderConDoc       renderContext       switchLayout conDeclSpn $ do-        -- the left arg haddock can use pipe only if the infix constructor has docs+        -- The left arg Haddock can use pipe style only if the infix+        -- constructor has docs.         if isJust con_doc           then do             mapM_ (p_hsDoc Pipe (With #endNewline)) larg_doc-            located lType p_hsType+            p_hsConDeclField l             breakpoint           else do-            located lType p_hsType+            p_hsConDeclField l             case larg_doc of               Just doc -> space >> p_hsDoc Caret (With #endNewline) doc               Nothing -> breakpoint@@ -242,7 +233,7 @@           case rarg_doc of             Just doc -> newline >> p_hsDoc Pipe (With #endNewline) doc             Nothing -> breakpoint-          located rType p_hsType+          p_hsConDeclField r   where     renderConDoc = mapM_ (p_hsDoc Pipe (With #endNewline)) con_doc     renderContext =@@ -262,13 +253,9 @@     conDeclSpn = conNameSpn : conArgsSpans     conNameSpn = getLocA con_name     conArgsSpans = case con_args of-      PrefixCon (_ :: [Void]) xs -> getLocA . hsScaledThing <$> xs+      PrefixCon xs -> getLocA . cdf_type <$> xs       RecCon l -> [getLocA l]-      InfixCon x y -> getLocA . hsScaledThing <$> [x, y]--    splitDocTy = \case-      L _ (HsDocTy _ ty doc) -> (ty, Just doc)-      ty -> (ty, Nothing)+      InfixCon x y -> getLocA . cdf_type <$> [x, y]  p_lhsContext ::   LHsContext GhcPs ->@@ -289,26 +276,27 @@ p_hsDerivingClause ::   HsDerivingClause GhcPs ->   R ()-p_hsDerivingClause HsDerivingClause {..} = do+p_hsDerivingClause HsDerivingClause {..} = multiLineIfDocumented deriv_clause_tys $ do   singleDerivingParens <- getPrinterOpt poSingleDerivingParens    txt "deriving"-  let derivingWhat = located deriv_clause_tys $ \case-        DctSingle NoExtField sigTy-          | DerivingAlways <- singleDerivingParens -> parens N $ located sigTy p_hsSigType-          | otherwise -> located sigTy p_hsSigType-        DctMulti NoExtField sigTys-          | [sigTy] <- sigTys,-            DerivingNever <- singleDerivingParens ->-              located sigTy p_hsSigType-          | otherwise -> do-              sortDerivedClasses <- getPrinterOpt poSortDerivedClasses-              let sort = if sortDerivedClasses then sortOn showOutputable else id-              parens N $-                sep-                  commaDel-                  (sitcc . located' p_hsSigType)-                  (sort sigTys)+  let derivingWhat = located deriv_clause_tys $ \tys ->+        multiLineIfDocumented tys $ case tys of+          DctSingle NoExtField sigTy+            | DerivingAlways <- singleDerivingParens -> parens N $ located sigTy p_hsSigType+            | otherwise -> located sigTy p_hsSigType+          DctMulti NoExtField sigTys+            | [sigTy] <- sigTys,+              DerivingNever <- singleDerivingParens ->+                located sigTy p_hsSigType+            | otherwise -> do+                sortDerivedClasses <- getPrinterOpt poSortDerivedClasses+                let sort = if sortDerivedClasses then sortOn showOutputable else id+                parens N $+                  sep+                    commaDel+                    (sitcc . located' p_hsSigType)+                    (sort sigTys)   space   case deriv_clause_strategy of     Nothing -> do@@ -337,26 +325,125 @@           located sigTy p_hsSigType  ----------------------------------------------------------------------------+-- FunRepr ConDeclGADT++-- | ConDecl, except should only be called with the ConDeclGADT constructor.+type ConDeclGADT = ConDecl++-- | FunRepr ConDeclGADT renders a GADT constructor type annotation, which looks+-- similar to a function type, except the arguments of the function have two+-- additional capabilities:+--   * They can specify UNPACK/strictness+--   * It can be a single argument with a record fields syntax+instance FunRepr (ConDeclGADT GhcPs) where+  parseFunRepr = \case+    L _ ConDeclGADT {..} ->+      fst+        . addSig+        . addOuter con_outer_bndrs+        . addInner con_inner_bndrs+        . addCtx con_mb_cxt+        . addArgs con_g_args+        $ mkRet con_res_ty+    _ -> error "parseFunRepr @ConDeclGADT unexpectedly called on non-GADT constructor"+    where+      addSig (next, loc) = (ParsedFunSig {sig = (), next}, loc)+      addOuter (L ann bndrs) =+        case bndrs :: HsOuterSigTyVarBndrs GhcPs of+          HsOuterImplicit {} -> id+          HsOuterExplicit _ bndrs' -> \(next, loc) ->+            let loc' = combineSrcSpans loc (getHasLoc ann)+                fun =+                  ParsedFunForall+                    { tele = L (l2l loc') $ mkHsForAllInvisTele noAnn bndrs',+                      next+                    }+             in (fun, loc')+      addInner =+        let go tele (next, loc) =+              let loc' =+                    combineSrcSpans loc $+                      case tele of+                        HsForAllVis x _ -> getHasLoc x+                        HsForAllInvis x _ -> getHasLoc x+                  fun = ParsedFunForall {tele = L (l2l loc') tele, next}+               in (fun, loc')+         in foldr (\tele acc -> go tele . acc) id+      addCtx = \case+        Nothing -> id+        Just ctxs -> \(next, loc) ->+          let loc' = combineSrcSpans loc (getHasLoc ctxs)+              fun = ParsedFunQuals {ctxs = [L (l2l loc') ctxs], next}+           in (fun, loc')+      addArgs details =+        case details :: HsConDeclGADTDetails GhcPs of+          PrefixConGADT _ fields ->+            let go field (next, loc) =+                  let loc' = combineSrcSpans loc (getHasLoc field.cdf_type)+                      fun =+                        ParsedFunArg+                          { span = l2l loc',+                            arg = L noAnn (Left field),+                            doc = field.cdf_doc,+                            multAnn = field.cdf_multiplicity,+                            next+                          }+                   in (fun, loc')+             in foldr (\field acc -> go field . acc) id fields+          RecConGADT _ fields -> \(next, loc) ->+            let loc' = combineSrcSpans (getHasLoc fields) loc+                fun =+                  ParsedFunArg+                    { span = l2l loc',+                      arg = la2la $ Right <$> fields,+                      doc = Nothing,+                      multAnn = HsUnannotated (EpArrow noAnn),+                      next+                    }+             in (fun, loc')+      mkRet ty =+        let fun =+              case ty of+                L _ (HsDocTy _ ret doc) -> ParsedFunReturn {ret, doc = Just doc}+                ret -> ParsedFunReturn {ret, doc = Nothing}+         in (fun, getHasLoc ty)++  type FunReprCtx (ConDeclGADT GhcPs) = HsType GhcPs+  renderFunReprCtx = p_hsType++  -- Invariant: Exactly one of the following must be true:+  --   * There's exactly one 'Right' arg+  --   * There are zero or more 'Left' args+  type FunReprArg (ConDeclGADT GhcPs) = Either (HsConDeclField GhcPs) [LocatedA (HsConDeclRecField GhcPs)]+  renderFunReprArg = either p_hsConDeclField p_hsConDeclRecFields++  type FunReprMult (ConDeclGADT GhcPs) = HsType GhcPs+  renderFunReprMult = p_hsType++  type FunReprRet (ConDeclGADT GhcPs) = HsType GhcPs+  renderFunReprRet = p_hsType++---------------------------------------------------------------------------- -- Helpers +-- | Do any of these constructors print a Haddock as @--@ lines where it+-- would share a line with the rest of the declaration?+--+-- Only the constructor's own Haddock and the docs on its prefix arguments+-- count. A record constructor lays its fields out over several lines+-- anyway, so documenting one of them says nothing about how the @=@ and the+-- constructor name should be arranged.+consHaveLineHaddocks :: [LConDecl GhcPs] -> R Bool+consHaveLineHaddocks = fmap or . traverse (f . unLoc)+  where+    f ConDeclH98 {..} =+      hasLineHaddocks $+        maybeToList con_doc <> case con_args of+          PrefixCon xs -> mapMaybe cdf_doc xs+          _ -> []+    f _ = pure False+ isInfix :: LexicalFixity -> Bool isInfix = \case   Infix -> True   Prefix -> False--hasHaddocks :: [LConDecl GhcPs] -> Bool-hasHaddocks = any (f . unLoc)-  where-    f ConDeclH98 {..} =-      isJust con_doc || case con_args of-        PrefixCon [] xs ->-          conArgsHaveHaddocks (hsScaledThing <$> xs)-        _ -> False-    f _ = False--conArgsHaveHaddocks :: [LBangType GhcPs] -> Bool-conArgsHaveHaddocks xs =-  let hasDocs = \case-        HsDocTy {} -> True-        _ -> False-   in any (hasDocs . unLoc) xs
src/Ormolu/Printer/Meat/Declaration/Foreign.hs view
@@ -9,10 +9,12 @@ import Control.Monad import GHC.Hs import GHC.Types.ForeignCall+import GHC.Types.SourceText import GHC.Types.SrcLoc import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common import Ormolu.Printer.Meat.Declaration.Signature+import Ormolu.Printer.Meat.Declaration.StringLiteral  p_foreignDecl :: ForeignDecl GhcPs -> R () p_foreignDecl = \case@@ -23,8 +25,8 @@     p_foreignExport fd_fe     p_foreignTypeSig fd --- | Printer for the last part of an import\/export, which is function name--- and type signature.+-- | Printer for the last part of an import\/export, which is the function+-- name and type signature. p_foreignTypeSig :: ForeignDecl GhcPs -> R () p_foreignTypeSig fd = do   breakpoint@@ -43,21 +45,24 @@ -- -- > foreign import callingConvention [safety] [identifier] ----- We need to check whether the safety has a good source, span, as it+-- We need to check whether the safety has a good source span, as it -- defaults to 'PlaySafe' if you don't have anything in the source. ----- We also layout the identifier using the 'SourceText', because printing--- with the other two fields of 'CImport' is very complicated. See the+-- We also lay out the identifier using the 'SourceText', because printing+-- it from the other two fields of 'CImport' is very complicated. See the -- 'Outputable' instance of 'ForeignImport' for details. p_foreignImport :: ForeignImport GhcPs -> R () p_foreignImport (CImport sourceText cCallConv safety _ _) = do   txt "foreign import"   space   located cCallConv atom-  -- Need to check for 'noLoc' for the 'safe' annotation+  -- Need to check for 'noLoc' for the 'safe' annotation.   when (isGoodSrcSpan $ getLocA safety) (space >> atom safety)-  space-  located sourceText p_sourceText+  inci $ located sourceText $ \case+    NoSourceText -> pure ()+    SourceText lit -> do+      breakpoint+      p_stringLit lit  p_foreignExport :: ForeignExport GhcPs -> R () p_foreignExport (CExport sourceText (L loc (CExportStatic _ _ cCallConv))) = do
src/Ormolu/Printer/Meat/Declaration/Instance.hs view
@@ -89,7 +89,7 @@         breakpoint         txt "where"   unless (null allDecls) . inci $ do-    -- Ensure whitespace is added after where clause.+    -- Ensure whitespace is added after the where clause.     breakpoint     dontUseBraces $ p_hsDeclsRespectGrouping Associated allDecls 
src/Ormolu/Printer/Meat/Declaration/OpTree.hs view
@@ -23,6 +23,7 @@ import GHC.Types.Name.Reader (RdrName, rdrNameOcc) import GHC.Types.SrcLoc import Ormolu.Config (poIndentation, poTrailingSectionOperators)+import Ormolu.Parser.CommentStream (LComment) import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common (p_rdrName) import Ormolu.Printer.Meat.Declaration.Value@@ -47,20 +48,20 @@ getOpNameStr :: RdrName -> String getOpNameStr = occNameString . rdrNameOcc --- | Decide if the operands of an operator chain should be hanging.+-- | Decide whether the operands of an operator chain should be hanging. opBranchPlacement ::   (HasLoc l) =>   -- | Placer function for nodes   (ty -> Placement) ->-  -- | first expression of the chain+  -- | First expression of the chain   OpTree (GenLocated l ty) op ->-  -- | last expression of the chain+  -- | Last expression of the chain   OpTree (GenLocated l ty) op ->   Placement opBranchPlacement placer firstExpr lastExpr-  -- If the beginning of the first argument and the last argument starts on-  -- the same line, and the second argument has a hanging form, use hanging-  -- placement.+  -- If the start of the first argument and the start of the last argument+  -- are on the same line, and the last argument has a hanging form, use+  -- hanging placement.   | isOneLineSpan       ( mkSrcSpan           (srcSpanStart (opTreeLoc firstExpr))@@ -70,7 +71,7 @@       placer n   | otherwise = Normal --- | Decide whether to use braces or not based on the layout and placement+-- | Decide whether or not to use braces based on the layout and placement -- of an expression in an infix operator application. opBranchBraceStyle :: Placement -> R (R () -> R ()) opBranchBraceStyle placement =@@ -117,33 +118,55 @@       couldBeTrailing (prevExpr, opi) =         -- Enabled by config         trailingSectionOperators-          -- An operator with fixity InfixR 0, like seq, $, and $ variants,-          -- is required+          -- An operator with fixity InfixR 0, like seq, $, and the $ variants,+          -- is required.           && isHardSplitterOp (opiFixityApproximation opi)-          -- the LHS must be single-line+          -- the LHS must be single-line.           && isOneLineSpan (opTreeLoc prevExpr)-          -- can only happen when a breakpoint would have been added anyway+          -- This can only happen when a breakpoint would have been added+          -- anyway.           && placement == Normal-          -- if the node just on the left of the operator (so the rightmost-          -- node of the subtree prevExpr) is a do-block, then we cannot-          -- place the operator in a trailing position (because it would be-          -- read as being part of the do-block)+          -- If the node just to the left of the operator (that is, the+          -- rightmost node of the subtree prevExpr) is a do-block, then we+          -- cannot place the operator in a trailing position, because it+          -- would be read as being part of the do-block.           && not (isDoBlock $ rightMostNode prevExpr)-      -- If all operators at the current level match the conditions to be-      -- trailing, then put them in a trailing position-      isTrailing = all couldBeTrailing $ zip (NE.toList exprs) ops+      -- A staircase of two or more trailing operators is only worthwhile when+      -- the operand at the very end of the chain has a hanging form (a do+      -- block, lambda, case, etc.): the trailing operator then introduces that+      -- block. When such a chain ends in an ordinary expression (a variable,+      -- literal, or plain application) the trailing layout only produces+      -- ever-deepening indentation, so we fall back to the leading-operator+      -- layout. A single hard splitter is exempt: it does not form a pyramid+      -- and trailing is the idiomatic way to introduce its operand.+      chainEndsInHangingForm =+        case rightMostNode t of+          OpNode (L _ n) -> exprPlacement n == Hanging+          _ -> False+      isSingleOperator = case ops of+        [_] -> True+        _ -> False+  -- A comment written on its own line in front of an operator forces the+  -- operator onto a line of its own. In trailing position that line starts+  -- at the indentation of the statement, and @$@ at the start of a line in+  -- a @do@ block is read as a new statement rather than as a continuation.+  -- The leading layout indents instead, so it keeps the meaning.+  opsAreCommented <-+    or <$> traverse (fmap (not . null) . leadingComments . opiOp) ops+  -- If all operators at the current level match the conditions to be+  -- trailing, and the chain is either a single operator or ends in a+  -- hanging form, then put the operators in a trailing position.+  let isTrailing =+        (isSingleOperator || chainEndsInHangingForm)+          && not opsAreCommented+          && all couldBeTrailing (zip (NE.toList exprs) ops)   ub <- if isTrailing then return useBraces else opBranchBraceStyle placement   indent <- getPrinterOpt poIndentation   let p_x = ub $ p_exprOpTree s firstExpr       putOpsExprs prevExpr (opi : ops') (expr : exprs') = do         let isLast = null exprs'             ub' = if not isLast then ub else id-            -- Distinguish holes used in infix notation.-            -- eg. '1 _foo 2' and '1 `_foo` 2'-            opWrapper = case unLoc (opiOp opi) of-              HsUnboundVar _ _ -> backticks-              _ -> id-            p_op = located (opiOp opi) (opWrapper . p_hsExpr)+            p_op = located (opiOp opi) p_hsExpr             p_y = ub' $ p_exprOpTree N expr         if isTrailing           then do@@ -181,6 +204,12 @@     p_x     putOpsExprs firstExpr ops otherExprs +-- | The comments that will be printed in front of a located thing.+leadingComments :: (HasLoc l) => GenLocated l a -> R [LComment]+leadingComments (L l _) = case locA l of+  RealSrcSpan spn _ -> getCommentsBefore spn+  _ -> pure []+ -- | Convert a 'LHsCmdTop' containing an operator tree to the 'OpTree' -- intermediate representation. cmdOpTree :: LHsCmdTop GhcPs -> OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs)@@ -220,14 +249,14 @@     p_x     putOpsExprs ops otherExprs --- | Check if given expression has a hanging form. Added for symmetry with--- exprPlacement and cmdTopPlacement, which are all used in p_xxxOpTree--- functions with opBranchPlacement.+-- | Check whether the given expression has a hanging form. Added for+-- symmetry with 'exprPlacement' and 'cmdTopPlacement', all of which are used+-- in the @p_xxxOpTree@ functions together with 'opBranchPlacement'. tyOpPlacement :: HsType GhcPs -> Placement tyOpPlacement = \case   _ -> Normal --- | Convert a LHsType containing an operator tree to the 'OpTree'+-- | Convert an 'LHsType' containing an operator tree to the 'OpTree' -- intermediate representation. tyOpTree :: LHsType GhcPs -> OpTree (LHsType GhcPs) (LocatedN RdrName) tyOpTree (L _ (HsOpTy _ _ l op r)) =
src/Ormolu/Printer/Meat/Declaration/RoleAnnotation.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} --- | Rendering of Role annotation declarations.+-- | Rendering of role annotation declarations. module Ormolu.Printer.Meat.Declaration.RoleAnnotation   ( p_roleAnnot,   )
src/Ormolu/Printer/Meat/Declaration/Rule.hs view
@@ -4,16 +4,16 @@  module Ormolu.Printer.Meat.Declaration.Rule   ( p_ruleDecls,+    p_ruleBndrs,   ) where -import Control.Monad (unless) import GHC.Hs import GHC.Types.Basic import GHC.Types.SourceText import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common-import Ormolu.Printer.Meat.Declaration.Signature+import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Signature import Ormolu.Printer.Meat.Declaration.Value import Ormolu.Printer.Meat.Type @@ -22,32 +22,34 @@   pragma "RULES" $ sep breakpoint (sitcc . located' p_ruleDecl) xs  p_ruleDecl :: RuleDecl GhcPs -> R ()-p_ruleDecl (HsRule _ ruleName activation tyvars ruleBndrs lhs rhs) = do+p_ruleDecl (HsRule _ ruleName activation ruleBndrs lhs rhs) = do   located ruleName p_ruleName   space   p_activation activation   space-  case tyvars of-    Nothing -> return ()-    Just xs -> do-      p_forallBndrs ForAllInvis p_hsTyVarBndr xs-      space-  -- It appears that there is no way to tell if there was an empty forall-  -- in the input or no forall at all. We do not want to add redundant-  -- foralls, so let's just skip the empty ones.-  unless (null ruleBndrs) $-    p_forallBndrs ForAllInvis p_ruleBndr ruleBndrs+  p_ruleBndrs ruleBndrs   breakpoint   inci $ do     located lhs p_hsExpr     space-    equals+    txt "="     inci $ do       breakpoint       located rhs p_hsExpr  p_ruleName :: RuleName -> R () p_ruleName name = atom (HsString NoSourceText name :: HsLit GhcPs)++p_ruleBndrs :: RuleBndrs GhcPs -> R ()+p_ruleBndrs (RuleBndrs HsRuleBndrsAnn {..} tyvars ruleBndrs) = do+  case tyvars of+    Nothing -> return ()+    Just xs -> do+      p_forallBndrs ForAllInvis p_hsTyVarBndr xs+      space+  case rb_tmanns of+    Nothing -> pure ()+    Just _ -> p_forallBndrs ForAllInvis p_ruleBndr ruleBndrs  p_ruleBndr :: RuleBndr GhcPs -> R () p_ruleBndr = \case
src/Ormolu/Printer/Meat/Declaration/Signature.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-}  -- | Type signature declarations. module Ormolu.Printer.Meat.Declaration.Signature@@ -8,18 +9,24 @@     p_typeAscription,     p_activation,     p_standaloneKindSig,+    deconstructExprFromSpecSigE,   ) where  import Control.Monad+import Data.Maybe (maybeToList) import GHC.Data.BooleanFormula import GHC.Hs+import GHC.Stack (HasCallStack) import GHC.Types.Basic import GHC.Types.Fixity import GHC.Types.Name.Reader import GHC.Types.SourceText+import GHC.Types.SrcLoc import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common+import Ormolu.Printer.Meat.Declaration.Rule+import Ormolu.Printer.Meat.Declaration.Value (p_hsExpr) import Ormolu.Printer.Meat.Type import Ormolu.Utils @@ -30,7 +37,9 @@   ClassOpSig _ def names sigType -> p_classOpSig def names sigType   FixSig _ sig -> p_fixSig sig   InlineSig _ name inlinePragma -> p_inlineSig name inlinePragma-  SpecSig _ name ts inlinePragma -> p_specSig name ts inlinePragma+  SpecSig _ name ts inlinePragma ->+    p_specSig Nothing (noLocA $ HsVar NoExtField name) ts inlinePragma+  SpecSigE _ ruleBndrs expr inlinePragma -> p_specSigE ruleBndrs expr inlinePragma   SpecInstSig _ sigType -> p_specInstSig sigType   MinimalSig _ booleanFormula -> p_minimalSig booleanFormula   CompleteMatchSig _ cs ty -> p_completeSig cs ty@@ -116,26 +125,71 @@   p_rdrName name  p_specSig ::-  -- | Name-  LocatedN RdrName ->+  -- | Rule binders+  Maybe (RuleBndrs GhcPs) ->+  -- | Expression to specialize+  LHsExpr GhcPs ->   -- | The types to specialize to   [LHsSigType GhcPs] ->   -- | For specialize inline   InlinePragma ->   R ()-p_specSig name ts InlinePragma {..} = pragmaBraces $ do+p_specSig mRuleBndrs specExpr ts InlinePragma {..} = pragmaBraces $ do   txt "SPECIALIZE"   space   p_inlineSpec inl_inline   space-  p_activation inl_act-  space-  p_rdrName name-  space-  token'dcolon-  breakpoint-  inci $ sep commaDel (located' p_hsSigType) ts+  case (inl_inline, inl_act) of+    (NoInline _, NeverActive) -> return ()+    _ -> p_activation inl_act+  inci $ do+    space+    forM_ mRuleBndrs $ \ruleBndrs -> do+      p_ruleBndrs ruleBndrs+      space+    located specExpr p_hsExpr+    case ts of+      [] -> pure ()+      _ -> do+        space+        token'dcolon+        breakpoint+        sep commaDel (located' p_hsSigType) ts +p_specSigE ::+  -- | Rule binders+  RuleBndrs GhcPs ->+  -- | Expression to specialize+  LHsExpr GhcPs ->+  -- | For specialize inline+  InlinePragma ->+  R ()+p_specSigE ruleBndrs expr =+  p_specSig (Just ruleBndrs) specExpr (maybeToList sigTy)+  where+    (_, specExpr, sigTy) = deconstructExprFromSpecSigE expr++-- | The 'LHsExpr' in a 'SpecSigE' can only be of a very specific form,+-- namely a variable applied to value/type-level arguments, optionally with a+-- type signature.+--+-- https://github.com/ghc-proposals/ghc-proposals/blob/e2c683698323cec3e33625369ae2b5f585387c70/proposals/0493-specialise-expressions.rst#2proposed-change-specification+deconstructExprFromSpecSigE ::+  (HasCallStack) =>+  LHsExpr GhcPs ->+  (LocatedN RdrName, LHsExpr GhcPs, Maybe (LHsSigType GhcPs))+deconstructExprFromSpecSigE = \case+  L _ (ExprWithTySig _ e HsWC {hswc_body}) ->+    (findVar e, e, Just hswc_body)+  e -> (findVar e, e, Nothing)+  where+    findVar :: LHsExpr GhcPs -> LocatedN RdrName+    findVar = \case+      L _ (HsVar _ name) -> name+      L _ (HsApp _ e _) -> findVar e+      L _ (HsAppType _ e _) -> findVar e+      _ -> error "unreachble"+ p_inlineSpec :: InlineSpec -> R () p_inlineSpec = \case   Inline _ -> txt "INLINE"@@ -165,7 +219,7 @@  p_minimalSig ::   -- | Boolean formula-  LBooleanFormula (LocatedN RdrName) ->+  LBooleanFormula GhcPs ->   R () p_minimalSig =   located' $ \booleanFormula ->@@ -173,7 +227,7 @@  p_booleanFormula ::   -- | Boolean formula-  BooleanFormula (LocatedN RdrName) ->+  BooleanFormula GhcPs ->   R () p_booleanFormula = \case   Var name -> p_rdrName name
+ src/Ormolu/Printer/Meat/Declaration/Signature.hs-boot view
@@ -0,0 +1,14 @@+module Ormolu.Printer.Meat.Declaration.Signature+  ( p_sigDecl,+    p_typeAscription,+    p_activation,+  )+where++import GHC.Hs+import GHC.Types.Basic+import Ormolu.Printer.Combinators++p_sigDecl :: Sig GhcPs -> R ()+p_typeAscription :: LHsSigType GhcPs -> R ()+p_activation :: Activation -> R ()
src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs view
@@ -18,8 +18,8 @@ import Ormolu.Printer.Combinators import Ormolu.Utils --- | Print the source text of a string literal while indenting gaps and newlines--- correctly.+-- | Print the source text of a string literal while indenting gaps and+-- newlines correctly. p_stringLit :: FastString -> R () p_stringLit src = case parseStringLiteral $ T.pack $ unpackFS src of   Nothing -> error $ "Internal Ormolu error: couldn't parse string literal: " <> show src@@ -40,12 +40,12 @@                   LastPos -> txt "\\" *> txt s         vlayout singleLine multiLine       MultilineStringLiteral ->-        sep breakpoint' txt segments+        sep newlineLiteral txt segments     txt endMarker --- | The start/end marker of the literal, whether it is a regular or a multiline--- literal, and the segments of the literals (separated by gaps for a regular--- literal, and separated by newlines for a multiline literal).+-- | The start/end marker of the literal, whether it is a regular or a+-- multiline literal, and the segments of the literal (separated by gaps for+-- a regular literal, and separated by newlines for a multiline literal). data ParsedStringLiteral = ParsedStringLiteral   { startMarker, endMarker :: Text,     stringLiteralKind :: StringLiteralKind,@@ -57,9 +57,9 @@ data StringLiteralKind = RegularStringLiteral | MultilineStringLiteral   deriving stock (Show, Eq) --- | Turn a string literal (as it exists in the source) into a more structured--- form for printing. This should never return 'Nothing' for literals that the--- GHC parser accepted.+-- | Turn a string literal (as it exists in the source) into a more+-- structured form for printing. This should never return 'Nothing' for+-- literals that the GHC parser accepted. parseStringLiteral :: Text -> Maybe ParsedStringLiteral parseStringLiteral = \s -> do   psl <-@@ -83,7 +83,7 @@           <|> ((marker,) <$> T.stripSuffix marker suffix)       pure ParsedStringLiteral {segments = [infix_], ..} -    -- Split a string on gaps (backslash delimited whitespaces).+    -- Split a string on gaps (backslash-delimited whitespace).     --     -- > splitGaps "bar\\  \\fo\\&o" == ["bar", "fo\\&o"]     splitGaps :: Text -> [Text]@@ -91,21 +91,34 @@       where         go [] = [s]         go ((pre, suf) : bs) = case T.uncons suf of-          Just ('\\', T.uncons -> Just (c, s'))-            | is_space c,-              let rest = T.drop 1 $ T.dropWhile (/= '\\') s' ->+          Just ('\\', suf')+            | (gap, T.uncons -> Just ('\\', rest)) <- T.span is_space suf',+              -- If there is a space after the backslash, this is definitely a+              -- string gap. Continue parsing gaps after the next backslash.+              not $ T.null gap ->                 pre : splitGaps rest-            | otherwise -> go $ (if c == '\\' then drop 1 else id) bs+            | otherwise ->+                -- Check whether @suf@ starts with an escape sequence+                -- involving another backslash. If so, it cannot be the start+                -- of a string gap, so we skip it.+                let skipNextBackslash =+                      any (`T.isPrefixOf` suf') escapesWithAnotherBackslash+                 in go $ (if skipNextBackslash then drop 1 else id) bs           _ -> go bs -    -- See the the MultilineStrings GHC proposal and 'lexMultilineString' from+        -- All escape sequences (without the initial backslash) with another+        -- backslash. See+        -- https://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-200002.6+        escapesWithAnotherBackslash = ["\\", "^\\"]++    -- See the MultilineStrings GHC proposal and 'lexMultilineString' from     -- "GHC.Parser.String" for reference.     --     -- https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0569-multiline-strings.rst#proposed-change-specification     splitMultilineString :: Text -> [Text]     splitMultilineString =       splitGaps-        -- There is no reason to use gaps with multiline string literals just to+        -- There is no reason to use gaps in multiline string literals just to         -- emulate multi-line strings, so we replace them with "\\ \\".         >>> intercalateMinimalStringGaps         >>> splitNewlines@@ -131,8 +144,8 @@              in pre : T.replicate fill " " : go (col' + fill) suf           _ -> [s] -    -- Don't touch the first line, and remove common whitespace from all-    -- remaining lines as well as convert those consisting only of whitespace to+    -- Don't touch the first line; remove common whitespace from all+    -- remaining lines, and convert those consisting only of whitespace into     -- empty lines.     rmCommonWhitespacePrefixAndBlank :: [Text] -> [Text]     rmCommonWhitespacePrefixAndBlank = \case
src/Ormolu/Printer/Meat/Declaration/Type.hs view
@@ -1,4 +1,7 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-}  -- | Rendering of type synonym declarations.@@ -7,6 +10,7 @@   ) where +import Data.Choice (pattern Is, pattern Isn't) import GHC.Hs.Extension import GHC.Hs.Type import GHC.Parser.Annotation@@ -32,13 +36,16 @@   space   switchLayout (getLocA name : map getLocA hsq_explicit) $     p_infixDefHelper-      (case fixity of Infix -> True; _ -> False)-      True+      ( case fixity of+          Infix -> Is #infixStyle+          _ -> Isn't #infixStyle+      )+      (Is #indentArgs)       (p_rdrName name)       (map (located' p_hsTyVarBndr) hsq_explicit)   inci $ do     space-    equals+    txt "="     if hasDocStrings (unLoc t)       then newline       else breakpoint
src/Ormolu/Printer/Meat/Declaration/TypeFamily.hs view
@@ -1,5 +1,8 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-}  -- | Rendering of data\/type families.@@ -10,6 +13,8 @@ where  import Control.Monad+import Data.Choice (pattern Is)+import Data.Choice qualified as Choice import Data.Maybe (isNothing) import GHC.Hs import GHC.Types.Fixity@@ -33,8 +38,8 @@     breakpoint     switchLayout headerSpns $ do       p_infixDefHelper-        (isInfix fdFixity)-        True+        (Choice.fromBool (isInfix fdFixity))+        (Is #indentArgs)         (p_rdrName fdLName)         (located' p_hsTyVarBndr <$> hsq_explicit)     let resultSig = p_familyResultSigL fdResultSig@@ -65,7 +70,7 @@   KindSig NoExtField k -> Just $ do     p_hsTypeAnnotation k   TyVarSig NoExtField bndr -> Just $ do-    equals+    txt "="     breakpoint     located bndr p_hsTyVarBndr @@ -93,13 +98,13 @@     let famLhsSpn = getLocA feqn_tycon : fmap lhsTypeArgSrcSpan feqn_pats     switchLayout famLhsSpn $       p_infixDefHelper-        (isInfix feqn_fixity)-        True+        (Choice.fromBool (isInfix feqn_fixity))+        (Is #indentArgs)         (p_rdrName feqn_tycon)         (p_lhsTypeArg <$> feqn_pats)     inci $ do       space-      equals+      txt "="       breakpoint       located feqn_rhs p_hsType 
src/Ormolu/Printer/Meat/Declaration/Value.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-}@@ -21,6 +22,7 @@  import Control.Monad import Data.Bool (bool)+import Data.Choice qualified as Choice import Data.Data hiding (Infix, Prefix) import Data.Function (on) import Data.Functor ((<&>))@@ -31,7 +33,6 @@ import Data.Maybe import Data.Text (Text) import Data.Text qualified as Text-import Data.Void import GHC.Data.Strict qualified as Strict import GHC.Hs import GHC.LanguageExtensions.Type (Extension (NegativeLiterals))@@ -47,7 +48,7 @@ import Ormolu.Printer.Meat.Common import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree-import Ormolu.Printer.Meat.Declaration.Signature+import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Signature import Ormolu.Printer.Meat.Declaration.StringLiteral import Ormolu.Printer.Meat.Type import Ormolu.Printer.Operators@@ -100,15 +101,15 @@   MatchGroup GhcPs (LocatedA body) ->   R () p_matchGroup' placer render style mg@MG {..} = do+  -- Since we are forcing braces on 'sepSemi' based on 'ob', we have to+  -- restore the brace state inside the 'sepSemi'.+  ub <- bool dontUseBraces useBraces <$> canUseBraces   let ob = case style of-        Case -> bracesIfEmpty-        LambdaCase -> bracesIfEmpty+        Case -> bracesIfNecessary+        LambdaCase -> bracesIfNecessary         _ -> dontUseBraces         where-          bracesIfEmpty = if isEmptyMatchGroup mg then useBraces else id-  -- Since we are forcing braces on 'sepSemi' based on 'ob', we have to-  -- restore the brace state inside the sepsemi.-  ub <- bool dontUseBraces useBraces <$> canUseBraces+          bracesIfNecessary = if isEmptyMatchGroup mg then useBraces else ub   ob $ sepSemi (located' (ub . p_Match)) (unLoc mg_alts)   where     p_Match m@Match {..} =@@ -117,31 +118,30 @@         render         (adjustMatchGroupStyle m style)         (isInfixMatch m)-        (HsNoMultAnn NoExtField)+        (HsUnannotated EpPatBind)         (matchStrictness m)         -- We use the spans of the individual patterns.         (unLoc m_pats)         m_grhss --- | Function id obtained through pattern matching on 'FunBind' should not--- be used to print the actual equations because the different ‘RdrNames’--- used in the equations may have different “decorations” (such as backticks--- and paretheses) associated with them. It is necessary to use per-equation--- names obtained from 'm_ctxt' of 'Match'. This function replaces function--- name inside of 'Function' accordingly.+-- | The function id obtained through pattern matching on 'FunBind' should+-- not be used to print the actual equations, because the different+-- ‘RdrNames’ used in the equations may have different “decorations” (such as+-- backticks and parentheses) associated with them. It is necessary to use+-- the per-equation names obtained from the 'm_ctxt' of a 'Match'. This+-- function replaces the function name inside 'Function' accordingly. adjustMatchGroupStyle ::   Match GhcPs body ->   MatchGroupStyle ->   MatchGroupStyle adjustMatchGroupStyle m = \case-  Function _ -> (Function . mc_fun . m_ctxt) m+  Function _ | FunRhs {mc_fun = f} <- m_ctxt m -> Function f   style -> style  matchStrictness :: Match id body -> SrcStrictness-matchStrictness match =-  case m_ctxt match of-    FunRhs {mc_strictness = s} -> s-    _ -> NoSrcStrict+matchStrictness = \case+  Match {m_ctxt = FunRhs {mc_strictness = s}} -> s+  _ -> NoSrcStrict  p_match ::   -- | Style of the group@@ -180,23 +180,25 @@   R () p_match' placer render style isInfix multAnn strictness m_pats GRHSs {..} = do   -- Normally, since patterns may be placed in a multi-line layout, it is-  -- necessary to bump indentation for the pattern group so it's more-  -- indented than function name. This in turn means that indentation for+  -- necessary to bump indentation for the pattern group so that it's more+  -- indented than the function name. This in turn means that indentation for   -- the body should also be bumped. Normally this would mean that bodies   -- would start with two indentation steps applied, which is ugly, so we-  -- need to be a bit more clever here and bump indentation level only when-  -- pattern group is multiline.+  -- need to be a bit more clever here and bump the indentation level only+  -- when the pattern group is multiline.+  p_hsMultAnn (located' p_hsType) multAnn   case multAnn of-    HsNoMultAnn NoExtField -> pure ()-    HsPct1Ann _ -> txt "%1" *> space-    HsMultAnn _ ty -> do-      txt "%"-      located ty p_hsType-      space+    HsUnannotated {} -> pure ()+    HsLinearAnn {} -> space+    HsExplicitMult {} -> space   case strictness of     NoSrcStrict -> return ()     SrcStrict -> txt "!"     SrcLazy -> txt "~"+  let isCase = \case+        Case -> True+        LambdaCase -> True+        _ -> False   indentBody <- case NE.nonEmpty m_pats of     Nothing ->       False <$ case style of@@ -207,14 +209,19 @@             Function name -> combineSrcSpans (getLocA name) patSpans             _ -> patSpans           patSpans = combineSrcSpans' (getLocA <$> ne_pats)-          indentBody = not (isOneLineSpan combinedSpans)+          containsOrPat = everything (||) $ \b -> case cast @_ @(Pat GhcPs) b of+            Just OrPat {} -> True+            _ -> False+          indentBody =+            not (isOneLineSpan combinedSpans)+              && not (isCase style && containsOrPat ne_pats)       switchLayoutNoLimit [combinedSpans] $ do         let stdCase = sep breakpoint (located' p_pat) m_pats         case style of           Function name ->             p_infixDefHelper-              isInfix-              indentBody+              (Choice.fromBool isInfix)+              (Choice.fromBool indentBody)               (p_rdrName name)               (located' p_pat <$> m_pats)           PatternBind -> stdCase@@ -237,21 +244,18 @@               -- lines, we have to indent all but the first pattern.               inci $ sep breakpoint (located' p_pat) tail_pats       return indentBody-  let -- Calculate position of end of patterns. This is useful when we decide-      -- about putting certain constructions in hanging positions.+  let -- Calculate the position of the end of the patterns. This is useful+      -- when we decide whether to put certain constructions in hanging+      -- positions.       endOfPats = case NE.nonEmpty m_pats of         Nothing -> case style of           Function name -> Just (getLocA name)           _ -> Nothing         Just pats -> (Just . getLocA . NE.last) pats-      isCase = \case-        Case -> True-        LambdaCase -> True-        _ -> False       hasGuards = withGuards grhssGRHSs       grhssSpan =         combineSrcSpans' $-          getGRHSSpan . unLoc <$> NE.fromList grhssGRHSs+          getGRHSSpan . unLoc <$> grhssGRHSs       patGrhssSpan =         maybe           grhssSpan@@ -277,20 +281,28 @@         sep           breakpoint           (located' (p_grhs' placement placer render groupStyle))-          grhssGRHSs+          (NE.toList grhssGRHSs)+      localBindsWhereSpan = case grhssLocalBinds of+        HsValBinds (EpAnn {anns = AnnList {al_rest}}) _ ->+          locA al_rest+        HsIPBinds (EpAnn {anns = AnnList {al_rest}}) _ ->+          locA al_rest+        EmptyLocalBinds _ -> noSrcSpan       p_where = do         unless (eqEmptyLocalBinds grhssLocalBinds) $ do           breakpoint           indentWhere <- getPrinterOpt poIndentWheres-          bool (inciByFrac (-1 / 2)) id indentWhere $ txt "where"+          bool (inciByFrac (-1 / 2)) id indentWhere $ do+            located (L localBindsWhereSpan ()) $ \_ -> txt "where"           breakpoint           inciIf indentWhere $ p_hsLocalBinds grhssLocalBinds   inciIf indentBody $ do     unless (length grhssGRHSs > 1) $       case style of         Function _ | hasGuards -> return ()-        Function _ -> space >> inci equals-        PatternBind -> space >> inci equals+        Function _ -> space >> inci (txt "=")+        PatternBind | hasGuards -> return ()+        PatternBind -> space >> inci (txt "=")         s | isCase s && hasGuards -> return ()         _ -> space >> token'rarrow     switchLayout [patGrhssSpan] $@@ -319,7 +331,7 @@       sitccIfTrailing (sep commaDel (sitcc . located' p_stmt) xs)       space       inci $ case style of-        EqualSign -> equals+        EqualSign -> txt "="         RightArrow -> token'rarrow       -- If we have a sequence of guards and it is placed in the normal way,       -- then we indent one level more for readability. Otherwise (all@@ -375,10 +387,10 @@     located cmd (p_hsCmd' Applicand s)     breakpoint     inci $ located expr p_hsExpr-  HsCmdLam _ variant mgroup -> p_lam isApp variant cmdPlacement p_hsCmd mgroup+  HsCmdLam _ variant mgroup -> p_lam isApp s variant cmdPlacement p_hsCmd mgroup   HsCmdPar _ c -> parens N $ sitcc $ located c p_hsCmd   HsCmdCase _ e mgroup ->-    p_case isApp cmdPlacement p_hsCmd e mgroup+    p_case isApp s cmdPlacement p_hsCmd e mgroup   HsCmdIf anns _ if' then' else' ->     p_if cmdPlacement p_hsCmd anns if' then' else'   HsCmdLet (letToken, _) localBinds c ->@@ -403,22 +415,22 @@   case getLocA l of     UnhelpfulSpan _ -> f x     RealSrcSpan currentSpn _ -> do-      getSpanMark >>= \case-        -- Spacing before comments will be handled by the code-        -- that prints comments, so we just have to deal with-        -- blank lines between statements here.-        Just (StatementSpan lastSpn) ->-          if srcSpanStartLine currentSpn > srcSpanEndLine lastSpn + 1-            then newline-            else return ()-        _ -> return ()+      (lastEmittedSpan <$> getLastEmitted) >>= \case+        -- We deal with blank lines between statements here. The last thing+        -- emitted may be a statement (the usual case) or a comment: the+        -- latter happens when the previous statement ended with a trailing+        -- comment, in which case we still want to preserve a blank line that+        -- followed that comment in the original input.+        Just lastSpn ->+          when (srcSpanStartLine currentSpn > srcSpanEndLine lastSpn + 1) newline+        Nothing -> return ()       f x-      -- In some cases the (f x) expression may insert a new mark. We want-      -- to be careful not to override comment marks.-      getSpanMark >>= \case-        Just (HaddockSpan _ _) -> return ()-        Just (CommentSpan _) -> return ()-        _ -> setSpanMark (StatementSpan currentSpn)+      -- In some cases the (f x) expression may record something else. We+      -- want to be careful not to override comments.+      getLastEmitted >>= \case+        LastEmittedHaddock _ -> return ()+        LastEmittedComment _ -> return ()+        _ -> setLastEmitted (LastEmittedStatement currentSpn)  p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R () p_stmt = p_stmt' N exprPlacement (p_hsExpr' NotApplicand)@@ -451,13 +463,13 @@   BodyStmt _ body _ _ -> located body (render s)   LetStmt epAnnLet binds -> p_let' True epAnnLet binds Nothing   ParStmt {} ->-    -- 'ParStmt' should always be eliminated in 'gatherStmts' already, such+    -- 'ParStmt' should always be eliminated in 'gatherStmts' already, so     -- that it never occurs in 'p_stmt''. Consequently, handling it here     -- would be redundant.     notImplemented "ParStmt"   TransStmt {..} ->-    -- 'TransStmt' only needs to account for render printing itself, since-    -- pretty printing of relevant statements (e.g., in 'trS_stmts') is+    -- 'TransStmt' only needs to account for printing itself, since+    -- pretty-printing of the relevant statements (e.g. in 'trS_stmts') is     -- handled through 'gatherStmts'.     case (trS_form, trS_by) of       (ThenForm, Nothing) -> do@@ -510,7 +522,7 @@         ub' $ withSpacing (p_stmt' s placer render) stmt         where           -- We need to set brace usage information for all but the last-          -- statement (e.g.in the case of nested do blocks).+          -- statement (e.g. in the case of nested do blocks).           ub' = case relPos of             FirstPos -> ub             MiddlePos -> ub@@ -523,8 +535,8 @@ p_hsLocalBinds = \case   HsValBinds epAnn (ValBinds _ binds lsigs) -> pseudoLocated epAnn $ do     -- When in a single-line layout, there is a chance that the inner-    -- elements will also contain semicolons and they will confuse the-    -- parser. so we request braces around every element except the last.+    -- elements will also contain semicolons that will confuse the parser,+    -- so we request braces around every element except the last.     br <- layoutToBraces <$> getLayout     let items =           let injectLeft (L l x) = L l (Left x)@@ -545,18 +557,18 @@     let p_ipBind (IPBind _ (L _ name) expr) = do           atom @HsIPName name           space-          equals+          txt "="           breakpoint           useBraces $ inci $ located expr p_hsExpr     sepSemi (located' p_ipBind) xs   EmptyLocalBinds _ -> return ()   where-    -- HsLocalBinds is no longer wrapped in a Located (see call sites-    -- of p_hsLocalBinds). Hence, we introduce a manual Located as we-    -- depend on the layout being correctly set.+    -- HsLocalBinds is no longer wrapped in a Located (see the call sites+    -- of p_hsLocalBinds). Hence, we introduce a manual Located, as we+    -- depend on the layout being set correctly.     pseudoLocated = \case       EpAnn {anns = AnnList {al_anchor}}-        | -- excluding cases where there are no bindings+        | -- Excluding cases where there are no bindings.           not $ isZeroWidthSpan (locA al_anchor) ->             located (L al_anchor ()) . const       _ -> id@@ -565,8 +577,8 @@ p_dotFieldOcc =   p_rdrName . fmap (mkVarUnqual . field_label) . dfoLabel -p_dotFieldOccs :: [DotFieldOcc GhcPs] -> R ()-p_dotFieldOccs = sep (txt ".") p_dotFieldOcc+p_dotFieldOccs :: NonEmpty (DotFieldOcc GhcPs) -> R ()+p_dotFieldOccs = sep (txt ".") p_dotFieldOcc . NE.toList  p_fieldOcc :: FieldOcc GhcPs -> R () p_fieldOcc FieldOcc {..} = p_rdrName foLabel@@ -580,7 +592,7 @@   p_lhs hfbLHS   unless hfbPun $ do     space-    equals+    txt "="     let placement =           if onTheSameLine (getLocA hfbLHS) (getLocA hfbRHS)             then exprPlacement (unLoc hfbRHS)@@ -590,8 +602,9 @@ p_hsExpr :: HsExpr GhcPs -> R () p_hsExpr = p_hsExpr' NotApplicand N --- | An applicand is the left-hand side in a function application, i.e. @f@ in--- @f a@. We need to track this in order to add extra identation in cases like+-- | An applicand is the left-hand side of a function application, i.e. @f@+-- in @f a@. We need to track this in order to add extra indentation in cases+-- like -- -- > foo = -- >   do@@ -604,10 +617,18 @@   Applicand -> inci . inci   NotApplicand -> inci +-- | Adjust bracing as needed for certain cases, e.g. those involving case+-- expressions and lambdas.+adjustBracing :: IsApplicand -> BracketStyle -> R () -> R ()+adjustBracing isApp s p = do+  layout <- getLayout+  case (s, layout, isApp) of+    (S, SingleLine, NotApplicand) -> useBraces p+    _ -> p+ p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R () p_hsExpr' isApp s = \case   HsVar _ name -> p_rdrName name-  HsUnboundVar _ occ -> atom occ   HsOverLabel sourceText _ -> do     txt "#"     p_sourceText sourceText@@ -622,10 +643,10 @@       HsMultilineString (SourceText stxt) _ -> p_stringLit stxt       r -> atom r   HsLam _ variant mgroup ->-    p_lam isApp variant exprPlacement p_hsExpr mgroup+    p_lam isApp s variant exprPlacement p_hsExpr mgroup   HsApp _ f x -> do     let -- In order to format function applications with multiple parameters-        -- nicer, traverse the AST to gather the function and all the+        -- more nicely, traverse the AST to gather the function and all the         -- parameters together.         gatherArgs f' knownArgs =           case f' of@@ -645,8 +666,7 @@         MultiLine -> Normal     -- If the last argument is not hanging, just separate every argument as     -- usual. If it is hanging, print the initial arguments and hang the-    -- last one. Also, use braces around the every argument except the last-    -- one.+    -- last one. Also, use braces around every argument except the last one.     case placement of       Normal -> do         let indentArg =@@ -701,13 +721,11 @@           _ -> False     txt "-"     -- If NegativeLiterals is enabled, we have to insert a space before-    -- negated literals, as `- 1` and `-1` have differing AST.+    -- negated literals, as `- 1` and `-1` have differing ASTs.     when (negativeLiterals && isLiteral) space     located e p_hsExpr-  HsPar _ e -> do-    csSpans <--      fmap (flip RealSrcSpan Strict.Nothing . getLoc) <$> getEnclosingComments-    switchLayout (locA e : csSpans) $+  HsPar _ e ->+    switchLayoutWithEnclosingComments [locA e] $       parens s (sitcc $ located e (dontUseBraces . p_hsExpr))   SectionL _ x op -> do     located x p_hsExpr@@ -743,13 +761,14 @@   ExplicitSum _ tag arity e ->     p_unboxedSum N tag arity (located e p_hsExpr)   HsCase _ e mgroup ->-    p_case isApp exprPlacement p_hsExpr e mgroup+    p_case isApp s exprPlacement p_hsExpr e mgroup   HsIf anns if' then' else' ->     p_if exprPlacement p_hsExpr anns if' then' else'   HsMultiIf _ guards -> do     txt "if"     breakpoint-    inciApplicand isApp $ sep breakpoint (located' (p_grhs RightArrow)) guards+    inciApplicand isApp $+      sep breakpoint (located' (p_grhs RightArrow)) (NE.toList guards)   HsLet (letToken, _) localBinds e ->     p_let (s == S) p_hsExpr letToken localBinds e   HsDo _ doFlavor es -> do@@ -775,7 +794,7 @@         dotdot = case rec_dotdot of           Just {} -> [txt ".."]           Nothing -> []-    inci . braces N $+    inci . recordBraces $       sep commaDel sitcc (fields <> dotdot)   RecordUpd {..} -> do     located rupd_expr p_hsExpr@@ -784,7 +803,7 @@           sep commaDel (sitcc . located' (p_hsFieldBind p_lbl))         p_fieldLabelStrings (FieldLabelStrings flss) =           p_dotFieldOccs $ unLoc <$> flss-    inci . braces N $ case rupd_flds of+    inci . recordBraces $ case rupd_flds of       RegularRecUpdFields {..} ->         p_recFields (located' p_fieldOcc) recUpdFields       OverloadedRecUpdFields {..} ->@@ -795,7 +814,7 @@     located gf_field p_dotFieldOcc   HsProjection {..} -> parens N $ do     txt "."-    p_dotFieldOccs (NE.toList proj_flds)+    p_dotFieldOccs proj_flds   ExprWithTySig _ x HsWC {hswc_body} -> sitcc $ do     located x p_hsExpr     inci $ p_hsTypeAnnotation (hsSigTypeToType <$> hswc_body)@@ -828,7 +847,7 @@     breakpoint'     txt "||]"   HsUntypedBracket _ x -> p_hsQuote x-  HsTypedSplice _ expr -> p_hsSpliceTH True expr DollarSplice+  HsTypedSplice _ (HsTypedSpliceExpr _ expr) -> p_hsSpliceTH True expr DollarSplice   HsUntypedSplice _ untySplice -> p_hsUntypedSplice DollarSplice untySplice   HsProc _ p e -> do     txt "proc"@@ -855,6 +874,9 @@     txt "type"     space     located hswc_body p_hsType+  HsHole holeKind -> case holeKind of+    HoleVar name -> p_rdrName name+    HoleError -> error "parse error"   -- similar to HsForAllTy   expr@HsForAll {} ->     p_hsFun expr@@ -867,7 +889,8 @@  -- | Print a list comprehension. ----- BracketStyle should be N except in a do-block, which must be S or else it's a parse error.+-- The 'BracketStyle' should be 'N' except in a do-block, where it must be 'S'+-- or else it's a parse error. p_listComp :: BracketStyle -> XRec GhcPs [ExprLStmt GhcPs] -> R () p_listComp s es = sitcc (vlayout singleLine multiLine)   where@@ -894,11 +917,11 @@       space       p_bodyParallels (gatherStmts stmts) -    -- print the list of list comprehension sections, e.g.+    -- Print the list of list comprehension sections, e.g.     -- [ "| x <- xs, y <- ys, let z = x <> y", "| a <- f z" ]     p_bodyParallels = sep (breakpoint >> txt "|" >> space) (sitccIfTrailing . p_bodyParallelStmts) -    -- print a list comprehension section within a pipe, e.g.+    -- Print a list comprehension section within a pipe, e.g.     -- [ "x <- xs", "y <- ys", "let z = x <> y" ]     p_bodyParallelStmts = sep commaDel (located' (sitcc . p_stmt)) @@ -937,7 +960,7 @@ -- @ -- -- The final expression is parsed out in p_body, and the rest is passed--- to this function. This function takes the above tree as input and+-- to this function. This function takes the tree above as input and -- normalizes it into: -- -- @@@ -954,17 +977,17 @@ -- -- Notes: --   * The number of elements in the outer list is the number of pipes in---     the comprehension; i.e. 1 unless -XParallelListComp is enabled+--     the comprehension, i.e. 1 unless -XParallelListComp is enabled. gatherStmts :: [ExprLStmt GhcPs] -> [[ExprLStmt GhcPs]] gatherStmts = \case-  -- When -XParallelListComp is enabled + list comprehension has-  -- multiple pipes, input will have exactly 1 element, and it-  -- will be ParStmt.+  -- When -XParallelListComp is enabled and the list comprehension has+  -- multiple pipes, the input will have exactly 1 element, and it+  -- will be a ParStmt.   [L _ (ParStmt _ blocks _ _)] ->     [ concatMap collectNonParStmts stmts-    | ParStmtBlock _ stmts _ _ <- blocks+    | ParStmtBlock _ stmts _ _ <- NE.toList blocks     ]-  -- Otherwise, list will not contain any ParStmt+  -- Otherwise, the list will not contain any ParStmt.   stmts ->     [ concatMap collectNonParStmts stmts     ]@@ -989,7 +1012,7 @@               located psb_def p_pat           ImplicitBidirectional ->             switchLayout pattern_def_spans $ do-              equals+              txt "="               breakpoint               located psb_def p_pat           ExplicitBidirectional mgroup -> do@@ -998,12 +1021,17 @@               breakpoint               located psb_def p_pat             breakpoint-            txt "where"+            indentWhere <- getPrinterOpt poIndentWheres+            let (inciWhere, inciBody) =+                  if indentWhere+                    then (id, inci)+                    else (inciByFrac (-1 / 2), id)+            inciWhere $ txt "where"             breakpoint-            inci (p_matchGroup (Function psb_id) mgroup)+            inciBody $ p_matchGroup (Function psb_id) mgroup   txt "pattern"   case psb_args of-    PrefixCon [] xs -> do+    PrefixCon xs -> do       space       p_rdrName psb_id       inci $ do@@ -1012,7 +1040,6 @@           unless (null xs) breakpoint           sitcc (sep breakpoint p_rdrName xs)         rhs conSpans-    PrefixCon (v : _) _ -> absurd v     RecCon xs -> do       space       p_rdrName psb_id@@ -1020,7 +1047,7 @@         let conSpans = getLocA . recordPatSynPatVar <$> xs         switchLayout conSpans $ do           unless (null xs) breakpointPreRecordBrace-          braces N $+          recordBraces $             sep commaDel (p_rdrName . recordPatSynPatVar) xs         rhs conSpans     InfixCon l r -> do@@ -1040,6 +1067,7 @@     Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA   ) =>   IsApplicand ->+  BracketStyle ->   -- | Placer   (body -> Placement) ->   -- | Render@@ -1049,20 +1077,23 @@   -- | Match group   MatchGroup GhcPs (LocatedA body) ->   R ()-p_case isApp placer render e mgroup = do+p_case isApp s placer render e mgroup = do   txt "case"   space   located e p_hsExpr   space   txt "of"   breakpoint-  inciApplicand isApp (p_matchGroup' placer render Case mgroup)+  adjustBracing isApp s $+    inciApplicand isApp (p_matchGroup' placer render Case mgroup)  p_lam ::   ( Anno (GRHS GhcPs (LocatedA body)) ~ EpAnnCO,     Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA   ) =>   IsApplicand ->+  -- | BracketStyle (S when inside a do block)+  BracketStyle ->   -- | Variant (@\\@ or @\\case@ or @\\cases@)   HsLamVariant ->   -- | Placer@@ -1072,7 +1103,7 @@   -- | Expression   MatchGroup GhcPs (LocatedA body) ->   R ()-p_lam isApp variant placer render mgroup = do+p_lam isApp s variant placer render mgroup = do   let mCaseTxt = case variant of         LamSingle -> Nothing         LamCase -> Just "\\case"@@ -1084,7 +1115,7 @@     Just caseTxt -> do       txt caseTxt       breakpoint-      inciApplicand isApp pMatchGroup+      adjustBracing isApp s (inciApplicand isApp pMatchGroup)  p_if ::   -- | Placer@@ -1106,7 +1137,12 @@   txt "if"   space   located if' p_hsExpr-  commentSpans <- fmap getLoc <$> getEnclosingComments+  -- A comment between the @then@ or @else@ keyword and its branch means the+  -- branch cannot hang; it has to start on its own line.+  commentSpans <-+    getEnclosingSpan >>= \case+      Nothing -> pure []+      Just enclosing -> fmap getLoc <$> getCommentsAnchoredWithin enclosing   let (thenSpan, elseSpan) = (locA aiThen, locA aiElse)         where           AnnsIf {aiThen, aiElse} = anns@@ -1243,65 +1279,66 @@         _ -> Nothing  p_pat :: Pat GhcPs -> R ()-p_pat = \case+p_pat = p_pat' False++p_pat' :: Bool -> Pat GhcPs -> R ()+p_pat' inAsPat = \case   WildPat _ -> txt "_"   VarPat _ name -> p_rdrName name   LazyPat _ pat -> do     txt "~"-    located pat p_pat+    located pat (p_pat' inAsPat)   AsPat _ name pat -> do     p_rdrName name     txt "@"-    located pat p_pat+    located pat (p_pat' True)   ParPat _ pat ->-    located pat (parens S . sitcc . p_pat)+    located pat (parens S . sitcc . p_pat' inAsPat)   BangPat _ pat -> do     txt "!"-    located pat p_pat+    located pat (p_pat' inAsPat)   ListPat _ pats ->-    brackets S $ sep commaDel (located' p_pat) pats+    brackets S $ sep commaDel (located' (p_pat' inAsPat)) pats   TuplePat _ pats boxing -> do     let parens' =           case boxing of             Boxed -> parens S             Unboxed -> parensHash S-    parens' $ sep commaDel (sitcc . located' p_pat) pats-  OrPat _ pats ->-    sepSemi (located' p_pat) (NE.toList pats)+    parens' $ sep commaDel (sitcc . located' (p_pat' inAsPat)) pats+  OrPat _ pats -> do+    sepSemi' inAsPat (located' (p_pat' inAsPat)) (NE.toList pats)   SumPat _ pat tag arity ->-    p_unboxedSum S tag arity (located pat p_pat)+    p_unboxedSum S tag arity (located pat (p_pat' inAsPat))   ConPat _ pat details ->     case details of-      PrefixCon tys xs -> sitcc $ do+      PrefixCon xs -> sitcc $ do         p_rdrName pat-        unless (null tys && null xs) breakpoint-        inci . sitcc $-          sep breakpoint (sitcc . either p_hsConPatTyArg (located' p_pat)) $-            (Left <$> tys) <> (Right <$> xs)+        unless (null xs) breakpoint+        inci . sitcc $ sep breakpoint (sitcc . located' (p_pat' inAsPat)) xs       RecCon (HsRecFields _ fields dotdot) -> do         p_rdrName pat         breakpointPreRecordBrace         let f = \case               Nothing -> txt ".."               Just x -> located x p_pat_hsFieldBind-        inci . braces N . sep commaDel f $+        inci . recordBraces . sep commaDel f $           case dotdot of             Nothing -> Just <$> fields             Just (L _ (RecFieldsDotDot n)) -> (Just <$> take n fields) ++ [Nothing]       InfixCon l r -> do         switchLayout [getLocA l, getLocA r] $ do-          located l p_pat+          located l (p_pat' inAsPat)           breakpoint           inci $ do             p_rdrName pat             space-            located r p_pat+            located r (p_pat' inAsPat)   ViewPat _ expr pat -> sitcc $ do     located expr p_hsExpr     space     token'rarrow     breakpoint-    inci (located pat p_pat)+    inci (located pat (p_pat' inAsPat))   SplicePat _ splice -> p_hsUntypedSplice DollarSplice splice   LitPat _ p -> atom p   NPat _ v (isJust -> isNegated) _ -> do@@ -1318,7 +1355,7 @@       space       located k (atom . ol_val)   SigPat _ pat HsPS {..} -> do-    located pat p_pat+    located pat (p_pat' inAsPat)     p_typeAscription (lhsTypeToSigType hsps_body)   EmbTyPat _ (HsTP _ ty) -> do     txt "type"@@ -1329,15 +1366,12 @@ p_tyPat :: HsTyPat GhcPs -> R () p_tyPat (HsTP _ ty) = txt "@" *> located ty p_hsType -p_hsConPatTyArg :: HsConPatTyArg GhcPs -> R ()-p_hsConPatTyArg (HsConPatTyArg _ patSigTy) = p_tyPat patSigTy- p_pat_hsFieldBind :: HsRecField GhcPs (LPat GhcPs) -> R () p_pat_hsFieldBind HsFieldBind {..} = do   located hfbLHS p_fieldOcc   unless hfbPun $ do     space-    equals+    txt "="     breakpoint     inci (located hfbRHS p_pat) @@ -1361,7 +1395,7 @@   HsUntypedSpliceExpr _ expr -> p_hsSpliceTH False expr deco   HsQuasiQuote _ quoterName str -> do     txt "["-    p_rdrName (noLocA quoterName)+    p_rdrName quoterName     txt "|"     -- QuasiQuoters often rely on precise custom strings. We cannot do any     -- formatting here without potentially breaking someone's code.@@ -1413,10 +1447,10 @@         breakpoint'         endQuote     -- With StarIsType, type and declaration brackets might end with a *,-    -- so we have to insert a space in the end to prevent the (mis)parsing+    -- so we have to insert a space at the end to prevent the (mis)parsing     -- of an (*|) operator.     -- The detection is a bit overcautious, as it adds the spaces as soon as-    -- HsStarTy is anywhere in the type/declaration.+    -- an HsStarTy appears anywhere in the type/declaration.     handleStarIsType :: (Data a) => a -> R () -> R ()     handleStarIsType a p       | containsHsStarTy a = space *> p <* space@@ -1428,21 +1462,31 @@  -- | Function types in expressions, e.g. with -XRequiredTypeArguments instance FunRepr (HsExpr GhcPs) where-  renderFunItem = p_hsExpr   parseFunRepr = \case     -- `forall a. _`     L ann (HsForAll _ tele expr) ->-      ParsedFunForall (L ann tele) (parseFunRepr expr)+      ParsedFunForall+        { tele = L ann tele,+          next = parseFunRepr expr+        }     -- `HasCallStack => _`     expr@(L _ HsQual {}) ->       let (ctxs, rest) = getContexts expr-       in ParsedFunQuals ctxs (parseFunRepr rest)+       in ParsedFunQuals+            { ctxs,+              next = parseFunRepr rest+            }     -- `Int -> _`-    expr@(L _ HsFunArr {}) ->-      let (args, ret) = getArgsAndReturn expr-       in ParsedFunArgs args (parseFunRepr ret)+    L ann (HsFunArr _ multAnn arg r) ->+      ParsedFunArg+        { span = ann,+          arg,+          doc = Nothing,+          multAnn,+          next = parseFunRepr r+        }     -- `_ -> Int`-    expr -> ParsedFunReturn (expr, Nothing)+    ret -> ParsedFunReturn {ret, doc = Nothing}     where       getContexts =         let go ctxs = \case@@ -1451,14 +1495,12 @@               expr ->                 (reverse ctxs, expr)          in go []-      getArgsAndReturn =-        let go args = \case-              L ann (HsFunArr _ arrow l r) ->-                go (L ann (l, Nothing, arrow) : args) r-              expr ->-                (reverse args, expr)-         in go [] +  renderFunReprArg = p_hsExpr+  renderFunReprCtx = p_hsExpr+  renderFunReprMult = p_hsExpr+  renderFunReprRet = p_hsExpr+ ---------------------------------------------------------------------------- -- Helpers @@ -1473,15 +1515,15 @@ getGRHSSpan (GRHS _ guards body) =   combineSrcSpans' $ getLocA body :| map getLocA guards --- | Determine placement of a given block.+-- | Determine the placement of a given block. blockPlacement ::   (body -> Placement) ->-  [LGRHS GhcPs (LocatedA body)] ->+  NonEmpty (LGRHS GhcPs (LocatedA body)) ->   Placement-blockPlacement placer [L _ (GRHS _ _ (L _ x))] = placer x+blockPlacement placer (L _ (GRHS _ _ (L _ x)) :| []) = placer x blockPlacement _ _ = Normal --- | Determine placement of a given command.+-- | Determine the placement of a given command. cmdPlacement :: HsCmd GhcPs -> Placement cmdPlacement = \case   HsCmdLam {} -> Hanging@@ -1489,14 +1531,14 @@   HsCmdDo {} -> Hanging   _ -> Normal --- | Determine placement of a top level command.+-- | Determine the placement of a top-level command. cmdTopPlacement :: HsCmdTop GhcPs -> Placement cmdTopPlacement (HsCmdTop _ (L _ x)) = cmdPlacement x --- | Check if given expression has a hanging form.+-- | Check whether the given expression has a hanging form. exprPlacement :: HsExpr GhcPs -> Placement exprPlacement = \case-  -- Only hang lambdas with single line parameter lists+  -- Only hang lambdas with single-line parameter lists.   HsLam _ variant mg -> case variant of     LamSingle -> case mg of       MG _ (L _ [L _ (Match _ _ (L _ (x : xs)) _)])@@ -1514,7 +1556,7 @@       _ -> Normal   HsApp _ _ y -> exprPlacement (unLoc y)   HsProc _ p _ ->-    -- Indentation breaks if pattern is longer than one line and left+    -- Indentation breaks if the pattern is longer than one line and left     -- hanging. Consequently, only apply hanging when it is safe.     if isOneLineSpan (getLocA p)       then Hanging@@ -1522,7 +1564,7 @@   _ -> Normal  -- | Return 'True' if any of the RHS expressions has guards.-withGuards :: [LGRHS GhcPs body] -> Bool+withGuards :: NonEmpty (LGRHS GhcPs body) -> Bool withGuards = any (checkOne . unLoc)   where     checkOne (GRHS _ [] _) = False@@ -1531,10 +1573,12 @@ -- | For use before record braces. Collapse to empty if not 'poRecordBraceSpace'. breakpointPreRecordBrace :: R () breakpointPreRecordBrace = do+  recordStyle <- getPrinterOpt poRecordStyle   useSpace <- getPrinterOpt poRecordBraceSpace-  if useSpace-    then breakpoint-    else breakpoint'+  if+    | recordStyle == RecordStyleKnr -> space+    | useSpace -> breakpoint+    | otherwise -> breakpoint'  -- | For nested lists/tuples, pad with whitespace so that we always indent correctly, -- rather than sometimes indenting by 2 regardless of 'poIndentation'.
src/Ormolu/Printer/Meat/ImportExport.hs view
@@ -8,6 +8,7 @@ module Ormolu.Printer.Meat.ImportExport   ( p_hsmodExports,     p_hsmodImport,+    enterMultilineLayoutIfContainsDocEntries,   ) where @@ -15,7 +16,6 @@ import Data.Choice (pattern Without) import Data.Foldable (for_, traverse_) import Data.List (inits)-import Data.Text qualified as T import GHC.Hs import GHC.LanguageExtensions.Type import GHC.Types.PkgQual@@ -28,12 +28,13 @@  p_hsmodExports :: [LIE GhcPs] -> R () p_hsmodExports xs =-  parens' False $ do-    layout <- getLayout-    sep-      breakpoint-      (\(isAllPrevDoc, p, l) -> sitcc (located (addDocSrcSpan l) (p_lie layout isAllPrevDoc p)))-      (withAllPrevDoc $ attachRelativePos xs)+  enterMultilineLayoutIfContainsDocEntries xs $+    parens' False $ do+      layout <- getLayout+      sep+        breakpoint+        (\(isAllPrevDoc, p, l) -> sitcc (located (addDocSrcSpan l) (p_lie layout isAllPrevDoc p)))+        (withAllPrevDoc $ attachRelativePos xs)   where     -- In order to correctly set the layout when a doc comment is present.     addDocSrcSpan lie@(L l ie) = case ieExportDoc ie of@@ -49,6 +50,10 @@   space   when ideclSafe (txt "safe")   space+  case ideclLevelSpec of+    LevelStylePre l -> p_declLevel l+    _ -> return ()+  space   when     (isImportDeclQualified ideclQualified && not useQualifiedPost)     (txt "qualified")@@ -59,6 +64,10 @@   space   inci $ do     located ideclName atom+    space+    case ideclLevelSpec of+      LevelStylePost l -> p_declLevel l+      _ -> return ()     when       (isImportDeclQualified ideclQualified && useQualifiedPost)       (space >> txt "qualified")@@ -72,19 +81,24 @@     space     case ideclImportList of       Nothing -> return ()-      Just (hiding, L _ xs) -> do+      Just (hiding, L listLoc xs) -> do         case hiding of           Exactly -> pure ()           EverythingBut -> txt "hiding"         breakIfNotDiffFriendly         parens' True $ do           layout <- getLayout+          when (null xs) $ locatedEmpty (locA listLoc)           sep             breakpoint             (\(p, l) -> sitcc (located l (p_lie layout False p)))             (attachRelativePos xs)-    newline +p_declLevel :: ImportDeclLevel -> R ()+p_declLevel = \case+  ImportDeclSplice -> txt "splice"+  ImportDeclQuote -> txt "quote"+ p_lie :: Layout -> Bool -> RelativePos -> IE GhcPs -> R () p_lie encLayout isAllPrevDoc relativePos = \case   IEVar mwarn l1 exportDoc -> do@@ -131,7 +145,7 @@   IEDoc NoExtField str ->     indentDoc $       p_hsDoc Pipe (Without #endNewline) str-  IEDocNamed NoExtField str -> indentDoc $ txt $ "-- $" <> T.pack str+  IEDocNamed NoExtField str -> indentDoc $ p_hsDocName str   where     -- Add a comma to a import-export list element     withComma m =@@ -182,6 +196,19 @@   IEGroup {} -> Nothing   IEDoc {} -> Nothing   IEDocNamed {} -> Nothing++enterMultilineLayoutIfContainsDocEntries :: [LIE GhcPs] -> R () -> R ()+enterMultilineLayoutIfContainsDocEntries xs =+  if any (isDocEntry . unLoc) xs+    then enterLayout MultiLine+    else id++isDocEntry :: (IE pass) -> Bool+isDocEntry = \case+  IEDoc {} -> True+  IEGroup {} -> True+  IEDocNamed {} -> True+  _ -> False  ---------------------------------------------------------------------------- 
src/Ormolu/Printer/Meat/Module.hs view
@@ -11,11 +11,15 @@ where  import Control.Monad-import Data.Choice (pattern With)+import Data.Choice (pattern Isn't, pattern With)+import Data.Choice qualified as Choice+import Data.Generics.Schemes (listify) import GHC.Hs hiding (comment)+import GHC.LanguageExtensions (Extension (ImplicitPrelude)) import GHC.Types.SrcLoc import Ormolu.Config import Ormolu.Imports (normalizeImports)+import Ormolu.Imports.Grouping (GroupImportsOpts (..)) import Ormolu.Parser.CommentStream import Ormolu.Parser.Pragma import Ormolu.Printer.Combinators@@ -26,7 +30,7 @@ import Ormolu.Printer.Meat.ImportExport import Ormolu.Printer.Meat.Pragma --- | Render a module-like entity (either a regular module or a backpack+-- | Render a module-like entity (either a regular module or a Backpack -- signature). p_hsModule ::   -- | Stack header@@ -40,27 +44,47 @@   let XModulePs {..} = hsmodExt       deprecSpan = maybe [] (pure . getLocA) hsmodDeprecMessage       exportSpans = maybe [] (pure . getLocA) hsmodExports-  switchLayout (deprecSpan <> exportSpans) $ do-    forM_ mstackHeader $ \(L spn comment) -> do-      spitCommentNow spn comment+  switchLayout (deprecSpan <> exportSpans) $+    enterMultilineLayoutIfContainsDocEntries (maybe [] unLoc hsmodExports) $ do+      forM_ mstackHeader $ \(L spn comment) -> do+        spitCommentNow SlotFloating spn comment+        newline       newline-    newline-    p_pragmas pragmas-    newline-    mapM_ (p_hsModuleHeader hsmod) hsmodName-    newline-    respectful <- getPrinterOpt poRespectful-    localModules <- getLocalModules-    importGrouping <- getPrinterOpt poImportGrouping-    forM_ (normalizeImports respectful localModules importGrouping hsmodImports) $ \importGroup -> do-      forM_ importGroup (located' p_hsmodImport)+      p_pragmas pragmas       newline-    declNewline-    switchLayout (getLocA <$> hsmodDecls) $ do-      preserveSpacing <- getPrinterOpt poRespectful-      (if preserveSpacing then p_hsDeclsRespectGrouping else p_hsDecls) Free hsmodDecls+      mapM_ (p_hsModuleHeader hsmod) hsmodName       newline-      spitRemainingComments+      importGroups <- normalizeImportsR hsmodImports+      forM_ importGroups $ \importGroup -> do+        -- The newline goes here rather than at the end of 'p_hsmodImport' so+        -- that a comment trailing an import is emitted while the printer is+        -- still on the import's line.+        forM_ importGroup $ \x -> do+          located' p_hsmodImport x+          newline+        newline+      declNewline+      switchLayout (getLocA <$> hsmodDecls) $ do+        preserveSpacing <- getPrinterOpt poRespectful+        (if preserveSpacing then p_hsDeclsRespectGrouping else p_hsDecls) Free hsmodDecls+        newline+        spitRemainingComments+  where+    normalizeImportsR imports = do+      implicitPrelude <- Choice.fromBool <$> isExtensionEnabled ImplicitPrelude+      respectful <- getPrinterOpt poRespectful+      localModules <- getLocalModules+      importGrouping <- getPrinterOpt poImportGrouping+      pure $+        normalizeImports+          GroupImportsOpts+            { grouping = importGrouping,+              respectful,+              allComments = listify (const True) hsmod+            }+          localModules+          implicitPrelude+          imports  p_hsModuleHeader :: HsModule GhcPs -> LocatedA ModuleName -> R () p_hsModuleHeader HsModule {hsmodExt = XModulePs {..}, ..} moduleName = do@@ -69,7 +93,7 @@       getPrinterOpt poHaddockStyleModule >>= \case         PrintStyleInherit -> getPrinterOpt poHaddockStyle         PrintStyleOverride style -> pure style-    forM_ hsmodHaddockModHeader (p_hsDoc' poHStyle Pipe (With #endNewline))+    forM_ hsmodHaddockModHeader (p_hsDocWith poHStyle Pipe (With #endNewline) (Isn't #mayShareLine))     p_hsmodName name    forM_ hsmodDeprecMessage $ \w -> do@@ -94,7 +118,8 @@     Nothing -> return ()     Just l -> do       breakpointBeforeExportList-      encloseLocated l $ \exports -> do+      located l $ \exports -> do+        when (null exports) $ locatedEmpty (locA l)         inci (p_hsmodExports exports)    breakpointBeforeWhere
src/Ormolu/Printer/Meat/Pragma.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}  -- | Pretty-printing of language pragmas. module Ormolu.Printer.Meat.Pragma@@ -55,9 +56,15 @@ p_pragmas ps = do   let prepare = L.sortOn snd . L.nub . concatMap analyze       analyze = \case+        -- @{-# LANGUAGE A, B #-}@ becomes one pragma per extension, but the+        -- comment written above it was written once. It goes to the first+        -- extension only; giving it to each of them printed it as many+        -- times as there were extensions.         (cs, PragmaLanguage xs) ->-          let f x = (cs, (Language (classifyLanguagePragma x), x))-           in f <$> xs+          let f x = (Language (classifyLanguagePragma x), x)+           in case xs of+                [] -> []+                (y : ys) -> (cs, f y) : ((mempty,) . f <$> ys)         (cs, PragmaOptionsGHC x) -> [(cs, (OptionsGHC, x))]         (cs, PragmaOptionsHaddock x) -> [(cs, (OptionsHaddock, x))]   forM_ (prepare ps) $ \(cs, (pragmaTy, x)) ->@@ -66,7 +73,7 @@ p_pragma :: [LComment] -> PragmaTy -> Text -> R () p_pragma comments ty x = do   forM_ comments $ \(L l comment) -> do-    spitCommentNow l comment+    spitCommentNow SlotPragma l comment     newline   txt "{-# "   txt $ case ty of
src/Ormolu/Printer/Meat/Type.hs view
@@ -1,10 +1,12 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-}  -- | Rendering of types. module Ormolu.Printer.Meat.Type@@ -16,42 +18,40 @@     p_hsTyVarBndr,     ForAllVisibility (..),     p_forallBndrs,-    p_conDeclFields,+    p_hsConDeclRecFields,+    p_hsConDeclField,+    p_hsConDeclFieldWithDoc,     p_lhsTypeArg,     p_hsSigType,-    FunRepr (..),-    ParsedFunRepr (..),-    p_hsFun,-    hsOuterTyVarBndrsToHsType,     hsSigTypeToType,     lhsTypeToSigType,++    -- * Re-exports from Ormolu.Printer.Meat.Type.Function+    FunRepr (..),+    ParsedFunRepr,+    ParsedFunRepr' (..),+    p_hsFun,   ) where  import Control.Monad-import Control.Monad.Cont qualified as Cont-import Control.Monad.State qualified as State-import Control.Monad.Trans qualified as Trans-import Data.Choice (Choice, pattern Is, pattern Isn't, pattern With, pattern Without)-import Data.Choice qualified as Choice-import Data.Foldable (traverse_)-import Data.Functor ((<&>))-import Data.List (sortOn)-import Data.Maybe (isJust)+import Data.Choice (pattern Is, pattern With, pattern Without)+import Data.Maybe (fromMaybe) import GHC.Data.Strict qualified as Strict import GHC.Hs hiding (isPromoted) import GHC.Types.SourceText import GHC.Types.SrcLoc import GHC.Types.Var-import GHC.Utils.Outputable (Outputable) import Ormolu.Config import Ormolu.Printer.Combinators import Ormolu.Printer.Meat.Common import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree (p_tyOpTree, tyOpTree) import Ormolu.Printer.Meat.Declaration.StringLiteral import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Value (p_hsUntypedSplice)+import Ormolu.Printer.Meat.Type.Function import Ormolu.Printer.Operators import Ormolu.Utils+import Prelude hiding (span)  p_hsType :: HsType GhcPs -> R () p_hsType = \case@@ -70,7 +70,7 @@     p_rdrName n   HsAppTy _ f x -> do     let -- In order to format type applications with multiple parameters-        -- nicer, traverse the AST to gather the function and all the+        -- more nicely, traverse the AST to gather the function and all the         -- parameters together.         gatherArgs f' knownArgs =           case f' of@@ -110,10 +110,8 @@     let opTree = BinaryOpBranches (tyOpTree x) op (tyOpTree y)     p_tyOpTree       (reassociateOpTree debug (Just . unLoc) modFixityMap opTree)-  HsParTy _ t -> do-    csSpans <--      fmap (flip RealSrcSpan Strict.Nothing . getLoc) <$> getEnclosingComments-    switchLayout (locA t : csSpans) $+  HsParTy _ t ->+    switchLayoutWithEnclosingComments [locA t] $       parens N (sitcc $ located t p_hsType)   HsIParamTy _ n t -> sitcc $ do     located n atom@@ -137,31 +135,8 @@     --       String     --       -- | Age     --       Int-    usePipe <--      getPrinterOpt poFunctionArrows <&> \case-        TrailingArrows -> True-        LeadingArrows -> False-        LeadingArgsArrows -> False-    if usePipe-      then do-        p_hsDoc Pipe (With #endNewline) str-        located t p_hsType-      else do-        located t p_hsType-        newline-        p_hsDoc Caret (Without #endNewline) str-  HsBangTy _ (HsBang u s) t -> do-    case u of-      SrcUnpack -> txt "{-# UNPACK #-}" >> space-      SrcNoUnpack -> txt "{-# NOUNPACK #-}" >> space-      NoSrcUnpack -> return ()-    case s of-      SrcLazy -> txt "~"-      SrcStrict -> txt "!"-      NoSrcStrict -> return ()-    located t p_hsType-  HsRecTy _ fields ->-    p_conDeclFields fields+    withHaddocks (Is #end) (Just str) $ do+      located t p_hsType   HsExplicitListTy _ p xs -> do     case p of       IsPromoted -> txt "'"@@ -189,7 +164,20 @@       HsStrTy (SourceText s) _ -> p_stringLit s       a -> atom a   HsWildCardTy _ -> txt "_"-  XHsType t -> atom t+  XHsType ext -> case ext of+    HsCoreTy t -> atom @HsCoreTy t+    HsBangTy _ (HsSrcBang _ u s) t -> do+      case u of+        SrcUnpack -> txt "{-# UNPACK #-}" >> space+        SrcNoUnpack -> txt "{-# NOUNPACK #-}" >> space+        NoSrcUnpack -> return ()+      case s of+        SrcLazy -> txt "~"+        SrcStrict -> txt "!"+        NoSrcStrict -> return ()+      located t p_hsType+    HsRecTy _ fields ->+      p_hsConDeclRecFields fields   where     startsWithSingleQuote = \case       HsAppTy _ (L _ f) _ -> startsWithSingleQuote f@@ -200,9 +188,14 @@       _ -> False  p_hsTypeAnnotation :: LHsType GhcPs -> R ()-p_hsTypeAnnotation = p_hsFunParsed . ParsedFunSig . parseFunRepr+p_hsTypeAnnotation ty =+  p_hsFunParsed @(HsType GhcPs) $+    ParsedFunSig+      { sig = (),+        next = parseFunRepr ty+      } --- | Return 'True' if at least one argument in 'HsType' has a doc string+-- | Return 'True' if at least one argument in the 'HsType' has a doc string -- attached to it. hasDocStrings :: HsType GhcPs -> Bool hasDocStrings = \case@@ -215,106 +208,55 @@ p_hsContext :: HsContext GhcPs -> R () p_hsContext = p_hsContext' p_hsType -p_hsContext' ::-  (Outputable (GenLocated (Anno a) a), HasLoc (Anno a)) =>-  (a -> R ()) ->-  [XRec GhcPs a] ->-  R ()-p_hsContext' f = \case-  [] -> txt "()"-  [x] -> located x f-  xs -> do-    shouldSort <- getPrinterOpt poSortConstraints-    let sort = if shouldSort then sortOn showOutputable else id-    parens N $ sep commaDel (sitcc . located' f) (sort xs)--class IsTyVarBndrFlag flag where-  isInferred :: flag -> Bool-  p_tyVarBndrFlag :: flag -> R ()-  p_tyVarBndrFlag _ = pure ()--instance IsTyVarBndrFlag () where-  isInferred () = False--instance IsTyVarBndrFlag Specificity where-  isInferred = \case-    InferredSpec -> True-    SpecifiedSpec -> False--instance IsTyVarBndrFlag (HsBndrVis GhcPs) where-  isInferred _ = False-  p_tyVarBndrFlag = \case-    HsBndrRequired NoExtField -> pure ()-    HsBndrInvisible _ -> txt "@"--p_hsTyVarBndr :: (IsTyVarBndrFlag flag) => HsTyVarBndr flag GhcPs -> R ()-p_hsTyVarBndr HsTvb {..} = do-  p_tyVarBndrFlag tvb_flag-  let wrap-        | isInferred tvb_flag = braces N-        | otherwise = case tvb_kind of-            HsBndrKind {} -> parens N-            HsBndrNoKind {} -> id-  wrap $ do-    case tvb_var of-      HsBndrVar _ x -> p_rdrName x-      HsBndrWildCard _ -> txt "_"-    case tvb_kind of-      HsBndrKind _ k -> inci $ p_hsTypeAnnotation k-      HsBndrNoKind _ -> pure ()--data ForAllVisibility = ForAllInvis | ForAllVis---- | Render several @forall@-ed variables.-p_forallBndrs ::-  (HasLoc l) =>-  ForAllVisibility ->-  (a -> R ()) ->-  [GenLocated l a] ->-  R ()-p_forallBndrs vis p tyvars = do-  p_forallBndrsStart p tyvars-  p_forallBndrsEnd (Without #extraSpace) vis--p_forallBndrsStart :: (HasLoc l) => (a -> R ()) -> [GenLocated l a] -> R ()-p_forallBndrsStart _ [] = token'forall-p_forallBndrsStart p tyvars = do-  switchLayout (locA <$> tyvars) $ do-    token'forall-    breakpoint-    inci $ do-      sitcc $ sep breakpoint (sitcc . located' p) tyvars--p_forallBndrsEnd :: Choice "extraSpace" -> ForAllVisibility -> R ()-p_forallBndrsEnd extraSpace = \case-  ForAllInvis -> txt dot >> space-  ForAllVis -> space >> token'rarrow-  where-    dot = if Choice.isTrue extraSpace then " ." else "."--p_conDeclFields :: [LConDeclField GhcPs] -> R ()-p_conDeclFields xs =-  braces N $ sep commaDel (sitcc . located' p_conDeclField) xs+p_hsConDeclRecFields :: [LHsConDeclRecField GhcPs] -> R ()+p_hsConDeclRecFields xs =+  multiLineIfDocumented xs . recordBraces $ do+    when (null xs) $+      getEnclosingSpan >>= mapM_ (locatedEmpty . flip RealSrcSpan Strict.Nothing)+    sep commaDel (sitcc . located' p_hsConDeclRecField) xs -p_conDeclField :: ConDeclField GhcPs -> R ()-p_conDeclField ConDeclField {..} = do-  commaStyle <- getPrinterOpt poCommaStyle-  when (commaStyle == Trailing) $-    mapM_ (p_hsDoc Pipe (With #endNewline)) cd_fld_doc+p_hsConDeclRecField :: HsConDeclRecField GhcPs -> R ()+p_hsConDeclRecField field@HsConDeclRecField {..} = withFieldHaddocks $ do   sitcc $     sep       commaDel       (located' (p_rdrName . foLabel))-      cd_fld_names-  inci $ p_hsTypeAnnotation cd_fld_type-  when (commaStyle == Leading) $-    mapM_ (inciByFrac (-1) . (newline >>) . p_hsDoc Caret (Without #endNewline)) cd_fld_doc+      cdrf_names+  inci $ p_hsFun field+  where+    withFieldHaddocks action = do+      commaStyle <- getPrinterOpt poCommaStyle+      let doc = cdf_doc cdrf_spec+      when (commaStyle == Trailing) $+        mapM_ (p_hsDocInline Pipe (With #endNewline)) doc+      action+      when (commaStyle == Leading) $+        mapM_ (inciByFrac (-1) . (newline >>) . p_hsDocInline Caret (Without #endNewline)) doc +-- | This does not print 'cdf_doc' and 'cdf_multiplicity', as there is no+-- single strategy for where to print them (see call sites).+p_hsConDeclField :: HsConDeclField GhcPs -> R ()+p_hsConDeclField CDF {..} = do+  case cdf_unpack of+    SrcUnpack -> txt "{-# UNPACK #-}" *> space+    SrcNoUnpack -> txt "{-# NOUNPACK #-}" *> space+    NoSrcUnpack -> pure ()+  located cdf_type $ \ty -> do+    case cdf_bang of+      SrcLazy -> txt "~"+      SrcStrict -> txt "!"+      NoSrcStrict -> pure ()+    p_hsType ty++p_hsConDeclFieldWithDoc :: HsConDeclField GhcPs -> R ()+p_hsConDeclFieldWithDoc cdf = withHaddocks (Is #end) cdf.cdf_doc $ do+  p_hsConDeclField cdf+ p_lhsTypeArg :: LHsTypeArg GhcPs -> R () p_lhsTypeArg = \case   HsValArg NoExtField ty -> located ty p_hsType-  -- first argument is the SrcSpan of the @,-  -- but the @ always has to be directly before the type argument+  -- The first argument is the SrcSpan of the @, but the @ always has to be+  -- directly before the type argument.   HsTypeArg _ ty -> txt "@" *> located ty p_hsType   -- NOTE(amesgen) is this unreachable or just not implemented?   HsArgPar _ -> notImplemented "HsArgPar"@@ -322,54 +264,37 @@ p_hsSigType :: HsSigType GhcPs -> R () p_hsSigType = p_hsType . hsSigTypeToType -------------------------------------------------------------------------------- Rendering function types---- | The parsed representation of a function-data ParsedFunRepr a-  = ParsedFunSig (ParsedFunRepr a)-  | ParsedFunForall-      (LocatedA (HsForAllTelescope GhcPs))-      (ParsedFunRepr a)-  | ParsedFunQuals-      [LocatedA (LocatedC [LocatedA a])]-      (ParsedFunRepr a)-  | -- | The argument, its optional docstring, and the arrow going to the next arg/return-    ParsedFunArgs-      [ LocatedA-          ( LocatedA a,-            Maybe (LHsDoc GhcPs),-            HsArrowOf (LocatedA a) GhcPs-          )-      ]-      (ParsedFunRepr a)-  | ParsedFunReturn-      ( LocatedA a,-        Maybe (LHsDoc GhcPs)-      )--class (Anno a ~ SrcSpanAnnA, Outputable a) => FunRepr a where-  renderFunItem :: a -> R ()--  parseFunRepr :: LocatedA a -> ParsedFunRepr a- instance FunRepr (HsType GhcPs) where-  renderFunItem = p_hsType   parseFunRepr = \case     -- `forall a. _`     L ann (HsForAllTy _ tele ty) ->-      ParsedFunForall (L ann tele) (parseFunRepr ty)+      ParsedFunForall+        { tele = L ann tele,+          next = parseFunRepr ty+        }     -- `HasCallStack => _`     ty@(L _ HsQualTy {}) ->       let (ctxs, rest) = getContexts ty-       in ParsedFunQuals ctxs (parseFunRepr rest)+       in ParsedFunQuals+            { ctxs,+              next = parseFunRepr rest+            }     -- `Int -> _`-    ty@(L _ HsFunTy {}) ->-      let (args, ret) = getArgsAndReturn ty-       in ParsedFunArgs args (parseFunRepr ret)+    L ann (HsFunTy _ multAnn l r) ->+      let (arg, doc) =+            case l of+              L _ (HsDocTy _ x doc_) -> (x, Just doc_)+              _ -> (l, Nothing)+       in ParsedFunArg+            { span = ann,+              arg,+              doc,+              multAnn,+              next = parseFunRepr r+            }     -- `_ -> Int`-    L _ (HsDocTy _ ty doc) -> ParsedFunReturn (ty, Just doc)-    ty -> ParsedFunReturn (ty, Nothing)+    L _ (HsDocTy _ ret doc) -> ParsedFunReturn {ret, doc = Just doc}+    ret -> ParsedFunReturn {ret, doc = Nothing}     where       getContexts =         let go ctxs = \case@@ -378,203 +303,106 @@               ty ->                 (reverse ctxs, ty)          in go []-      getArgsAndReturn =-        let go args = \case-              L ann (HsFunTy _ arrow (L _ (HsDocTy _ l doc)) r) ->-                go (L ann (l, Just doc, arrow) : args) r-              L ann (HsFunTy _ arrow l r) ->-                go (L ann (l, Nothing, arrow) : args) r-              ty ->-                (reverse args, ty)-         in go [] --- | For implementing function-arrows and related configuration, we'll collect--- all the components of the function type first, then render as a block instead--- of rendering each part independently, which will let us track local state--- within a function type.------ This function should be passed the first function-related construct we find;--- see FunRepr for more details.-p_hsFun :: (FunRepr a) => a -> R ()-p_hsFun = p_hsFunParsed . parseFunRepr . L (noAnn @SrcSpanAnnA)--p_hsFunParsed :: (FunRepr a) => ParsedFunRepr a -> R ()-p_hsFunParsed fun = do-  arrowsStyle <- getPrinterOpt poFunctionArrows-  p_hsFunParsed' arrowsStyle fun--type PrintHsFun x = State.StateT PrintHsFunState (Cont.ContT () R) x--type PrintHsFunState =-  ( Maybe (R ()), -- The leading delimiter to output at the next line-    Choice "forceMultiline"-  )--p_hsFunParsed' ::-  (FunRepr a) =>-  FunctionArrowsStyle ->-  ParsedFunRepr a ->-  R ()-p_hsFunParsed' arrowsStyle fun0 = Cont.evalContT . (`State.evalStateT` initialState) . go $ fun0-  where-    go = \case-      ParsedFunSig fun' -> do-        -- Should only happen at the very beginning, if at all-        p_parsedFunSig-        setMultilineContext fun'-        go fun'-      ParsedFunForall tele fun' -> do-        p_parsedFunForall tele-        setMultilineContext fun'-        go fun'-      ParsedFunQuals ctxs fun' -> do-        p_parsedFunQuals ctxs-        setMultilineContext fun'-        go fun'-      ParsedFunArgs args fun' -> do-        p_parsedFunArgs args-        go fun'-      ParsedFunReturn ret -> do-        p_parsedFunReturn ret--    liftR = Trans.lift . Trans.lift-    initialState = (Nothing, Without #forceMultiline)--    withApplyLeadingDelim f = do-      (leadingDelim, _) <- State.get-      a <- f leadingDelim-      State.modify $ \(_, x) -> (Nothing, x)-      pure a-    applyLeadingDelim = withApplyLeadingDelim (traverse_ liftR)--    setLeadingDelim delim =-      State.modify $ \(_, x) -> (Just delim, x)--    getIsMultiline = do-      (_, forceMultiline) <- State.get-      layout <- liftR getLayout-      pure $ Choice.toBool forceMultiline || layout == MultiLine--    setMultilineContext fun =-      State.modify $ \(x, _) -> (x, Choice.fromBool $ hasDocs fun)--    -- Make everything afterwards occur within a located block, with the-    -- magic of ContT. `item <- setLocated litem; ...` is equivalent to-    -- `located litem $ \item -> ...`.-    setLocated :: (HasLoc ann) => GenLocated ann x -> PrintHsFun x-    setLocated litem = Trans.lift $ Cont.ContT (located litem)--    p_parsedFunSig = do-      if isTrailing (Isn't #argDelim)-        then liftR $ do-          space >> token'dcolon-          if hasDocs fun0 then newline else breakpoint-        else do-          liftR breakpoint-          setLeadingDelim (token'dcolon >> space)--    p_parsedFunForall x@(L _ tele) = do-      void $ setLocated x-      applyLeadingDelim-      vis <--        case tele of-          HsForAllInvis _ bndrs -> do-            liftR $ p_forallBndrsStart p_hsTyVarBndr bndrs-            pure ForAllInvis-          HsForAllVis _ bndrs -> do-            liftR $ p_forallBndrsStart p_hsTyVarBndr bndrs-            pure ForAllVis-      isMultiline <- getIsMultiline-      if isTrailing (Isn't #argDelim) || not isMultiline-        then do-          liftR $ p_forallBndrsEnd (Without #extraSpace) vis-          interArgBreak-        else do-          interArgBreak-          let extraSpace = if isMultiline then With #extraSpace else Without #extraSpace-          setLeadingDelim (p_forallBndrsEnd extraSpace vis)--    p_parsedFunQuals ctxs = do-      -- we only want to set located on the first context-      case ctxs of-        ctx : _ -> void $ setLocated ctx-        _ -> pure ()--      forM_ ctxs $ \(L _ lctx) -> do-        applyLeadingDelim-        liftR $ located lctx (p_hsContext' renderFunItem)-        if isTrailing (Isn't #argDelim)-          then do-            liftR $ space >> token'darrow-            interArgBreak-          else do-            interArgBreak-            setLeadingDelim (token'darrow >> space)--    p_parsedFunArgs args = do-      -- we only want to set located on the first arg-      case args of-        arg : _ -> void $ setLocated arg-        _ -> pure ()+  renderFunReprCtx = p_hsType+  renderFunReprArg = p_hsType+  renderFunReprMult = p_hsType+  renderFunReprRet = p_hsType -      forM_ args $ \(L _ (larg, doc, arrow)) -> do-        let renderArrow = p_arrow (located' renderFunItem) arrow-        withHaddocks (Isn't #end) doc $ do-          withApplyLeadingDelim $ \applyLeadingDelim' ->-            liftR . located larg $ \arg -> do-              traverse_ id applyLeadingDelim'-              renderFunItem arg-          if isTrailing (Is #argDelim)-            then do-              liftR $ space >> renderArrow-              interArgBreak-            else do-              interArgBreak-              setLeadingDelim (renderArrow >> space)+----------------------------------------------------------------------------+-- FunRepr HsConDeclRecField -    p_parsedFunReturn (ret, doc) = do-      void $ setLocated ret-      withHaddocks (Is #end) doc $ do-        applyLeadingDelim-        liftR $ located ret renderFunItem+-- | FunRepr HsConDeclRecField renders a record field type annotation. If the+-- record field has a function type, we want to render it as a function+-- respecting `function-arrows`. For the most part, it behaves like HsTypeSig;+-- however, record fields can have additional syntax not in normal function+-- types, like specifying the multiplicity for the `::` or specifying+-- UNPACK/strictness, so we need to handle this specially.+instance FunRepr (HsConDeclRecField GhcPs) where+  parseFunRepr (L _ HsConDeclRecField {..}) =+    ParsedFunSig+      { sig = cdrf_spec.cdf_multiplicity,+        next = toRecFieldRepr False $ parseFunRepr cdrf_spec.cdf_type+      }+    where+      toRecFieldRepr :: Bool -> ParsedFunRepr (HsType GhcPs) -> ParsedFunRepr (HsConDeclRecField GhcPs)+      toRecFieldRepr seenArg = \case+        ParsedFunSig {} -> error "parseFunRepr @HsType unexpectedly returned ParsedFunSig"+        ParsedFunForall {..} ->+          ParsedFunForall+            { tele,+              next = toRecFieldRepr seenArg next+            }+        ParsedFunQuals {..} ->+          ParsedFunQuals+            { ctxs,+              next = toRecFieldRepr seenArg next+            }+        ParsedFunArg {..} ->+          ParsedFunArg+            { span,+              arg =+                let mUnpack =+                      if not seenArg+                        then Just cdrf_spec.cdf_unpack+                        else Nothing+                 in L (getLoc arg) (mUnpack, arg),+              doc,+              multAnn,+              next = toRecFieldRepr True next+            }+        ParsedFunReturn {..} ->+          ParsedFunReturn+            { ret =+                let mAnn =+                      if not seenArg+                        then Just (cdrf_spec.cdf_unpack, cdrf_spec.cdf_bang)+                        else Nothing+                 in L (getLoc ret) (mAnn, ret),+              doc+            } -    withHaddocks isEnd doc m = do-      isLeadingHaddock <--        liftR $-          getPrinterOpt poHaddockLocSignature <&> \case-            HaddockLocSigAuto ->-              if isTrailing (Is #argDelim) then With #pipe else Without #pipe-            HaddockLocSigLeading ->-              With #pipe-            HaddockLocSigTrailing ->-              Without #pipe+  type FunReprSig (HsConDeclRecField GhcPs) = HsMultAnnOf (LocatedA (HsType GhcPs)) GhcPs+  renderFunReprSig multAnn = do+    space+    p_hsMultAnn (located' p_hsType) multAnn+    space+    token'dcolon -      if Choice.isTrue isLeadingHaddock-        then do-          traverse (liftR . p_hsDoc Pipe (With #endNewline)) doc *> m-        else do-          let (pre, endNewline) =-                if Choice.isTrue isEnd-                  then (when (isJust doc) (liftR newline), Without #endNewline)-                  else (pure (), With #endNewline)-          m <* pre <* traverse (liftR . p_hsDoc Caret endNewline) doc+  type FunReprCtx (HsConDeclRecField GhcPs) = HsType GhcPs+  renderFunReprCtx = p_hsType -    interArgBreak = do-      isMultiline <- getIsMultiline-      liftR $ if isMultiline then newline else breakpoint+  -- Invariant: SrcUnpackedness should only be set for the first arg+  type FunReprArg (HsConDeclRecField GhcPs) = (Maybe SrcUnpackedness, LocatedA (HsType GhcPs))+  renderFunReprArg (mUnpacked, ty) =+    p_hsConDeclField+      CDF+        { cdf_ext = error "unused",+          cdf_unpack = fromMaybe NoSrcUnpack mUnpacked,+          cdf_bang = NoSrcStrict,+          cdf_multiplicity = error "unused",+          cdf_type = ty,+          cdf_doc = error "unused"+        } -    hasDocs = \case-      ParsedFunSig fun -> hasDocs fun-      ParsedFunForall _ fun -> hasDocs fun-      ParsedFunQuals _ fun -> hasDocs fun-      ParsedFunArgs args fun -> or [isJust doc | L _ (_, doc, _) <- args] || hasDocs fun-      ParsedFunReturn (_, doc) -> isJust doc+  type FunReprMult (HsConDeclRecField GhcPs) = HsType GhcPs+  renderFunReprMult = p_hsType -    isTrailing isArgDelim =-      case arrowsStyle of-        TrailingArrows -> True-        LeadingArgsArrows -> not $ Choice.isTrue isArgDelim-        LeadingArrows -> False+  -- Invariant: SrcUnpackedness/SrcStrictness should only be set if there are+  -- no args+  type FunReprRet (HsConDeclRecField GhcPs) = (Maybe (SrcUnpackedness, SrcStrictness), LocatedA (HsType GhcPs))+  renderFunReprRet (mAnn, ty) =+    p_hsConDeclField+      CDF+        { cdf_ext = error "unused",+          cdf_unpack = unpack,+          cdf_bang = strictness,+          cdf_multiplicity = error "unused",+          cdf_type = ty,+          cdf_doc = error "unused"+        }+    where+      (unpack, strictness) = fromMaybe (NoSrcUnpack, NoSrcStrict) mAnn  ---------------------------------------------------------------------------- -- Conversion functions
+ src/Ormolu/Printer/Meat/Type/Function.hs view
@@ -0,0 +1,493 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE NoFieldSelectors #-}+{-# OPTIONS_GHC -Wno-partial-fields #-}++-- | A Fourmolu-specific module for rendering function-like type signatures.+module Ormolu.Printer.Meat.Type.Function+  ( -- * PrintHsFun+    PrintHsFun,+    MonadR,++    -- * ParsedFunRepr+    ParsedFunRepr,+    ParsedFunRepr' (..),+    FunRepr (..),+    p_hsFun,+    p_hsFunParsed,++    -- * forall+    ForAllVisibility (..),+    p_forallBndrs,++    -- * TyVarBndr+    p_hsTyVarBndr,++    -- * Contexts+    p_hsContext',++    -- * Haddocks+    withHaddocks,+  )+where++import Control.Monad (forM_, void, when)+import Control.Monad.Cont qualified as Cont+import Control.Monad.State qualified as State+import Control.Monad.Trans qualified as Trans+import Data.Choice (Choice, pattern Is, pattern Isn't, pattern With, pattern Without)+import Data.Choice qualified as Choice+import Data.Foldable (traverse_)+import Data.Functor ((<&>))+import Data.List (sortOn)+import Data.Maybe (isJust)+import GHC.Hs+import GHC.Types.SrcLoc (GenLocated (..))+import GHC.Types.Var (Specificity (..))+import GHC.Utils.Outputable (Outputable)+import Ormolu.Config+import Ormolu.Printer.Combinators+import Ormolu.Printer.Meat.Common (p_arrow, p_hsDocInline, p_rdrName)+import Ormolu.Utils (showOutputable)+import Prelude hiding (span)++{----- PrintHsFun -----}++newtype PrintHsFun a = PrintHsFun (State.StateT PrintHsFunState (Cont.ContT () R) a)+  deriving newtype (Functor, Applicative, Monad)++data PrintHsFunState = PrintHsFunState+  { -- | The leading delimiter to output at the next line+    leadingDelim :: Maybe (R ()),+    forceMultiline :: Bool,+    -- | Whether we're currently printing args+    inArgList :: Bool+  }++runPrintHsFun :: PrintHsFun () -> R ()+runPrintHsFun (PrintHsFun m) = Cont.evalContT . (`State.evalStateT` initialState) $ m+  where+    initialState =+      PrintHsFunState+        { leadingDelim = Nothing,+          forceMultiline = False,+          inArgList = False+        }++class (Monad m) => MonadR m where+  liftR :: R a -> m a++instance MonadR R where+  liftR = id++instance MonadR PrintHsFun where+  liftR = PrintHsFun . Trans.lift . Trans.lift++withApplyLeadingDelim :: (Maybe (R ()) -> PrintHsFun a) -> PrintHsFun a+withApplyLeadingDelim f = do+  state <- PrintHsFun State.get+  a <- f state.leadingDelim+  PrintHsFun . State.put $ state {leadingDelim = Nothing}+  pure a++applyLeadingDelim :: PrintHsFun ()+applyLeadingDelim = withApplyLeadingDelim (traverse_ liftR)++setLeadingDelim :: R () -> PrintHsFun ()+setLeadingDelim delim =+  PrintHsFun . State.modify $ \state -> state {leadingDelim = Just delim}++getIsMultiline :: PrintHsFun Bool+getIsMultiline = do+  state <- PrintHsFun State.get+  layout <- liftR getLayout+  pure $ state.forceMultiline || layout == MultiLine++setMultilineContext :: ParsedFunRepr' sig ctx arg mult ret -> PrintHsFun ()+setMultilineContext fun =+  PrintHsFun . State.modify $ \state -> state {forceMultiline = hasDocs fun}++getIsTrailing :: (MonadR m) => m (Choice "argDelim" -> Bool)+getIsTrailing = do+  arrowsStyle <- liftR $ getPrinterOpt poFunctionArrows+  pure $ \isArgDelim ->+    case arrowsStyle of+      TrailingArrows -> True+      LeadingArgsArrows -> not $ Choice.isTrue isArgDelim+      LeadingArrows -> False++getInArgList :: PrintHsFun Bool+getInArgList = PrintHsFun . State.gets $ (.inArgList)++setInArgList :: Bool -> PrintHsFun ()+setInArgList x = PrintHsFun . State.modify $ \state -> state {inArgList = x}++-- Make everything afterwards occur within a located block, with the+-- magic of ContT. `item <- setLocated litem; ...` is equivalent to+-- `located litem $ \item -> ...`.+setLocated :: (HasLoc ann) => GenLocated ann x -> PrintHsFun x+setLocated litem = PrintHsFun . Trans.lift $ Cont.ContT (located litem)++setLocated_ :: (HasLoc ann) => GenLocated ann x -> PrintHsFun ()+setLocated_ = void . setLocated++{----- ParsedFunRepr -----}++type ParsedFunRepr a =+  ParsedFunRepr'+    (FunReprSig a)+    (FunReprCtx a)+    (FunReprArg a)+    (FunReprMult a)+    (FunReprRet a)++-- | The parsed representation of a function+data ParsedFunRepr' sig ctx arg mult ret+  = -- | The "::" delimiter. Invariant: must be first.+    ParsedFunSig+      { sig :: sig,+        next :: ParsedFunRepr' sig ctx arg mult ret+      }+  | -- | The "forall a b." or "forall a b ->" construct.+    ParsedFunForall+      { tele :: LocatedA (HsForAllTelescope GhcPs),+        next :: ParsedFunRepr' sig ctx arg mult ret+      }+  | -- | The "(A, B) =>" construct.+    ParsedFunQuals+      { ctxs :: [LocatedA (LocatedC [LocatedA ctx])],+        next :: ParsedFunRepr' sig ctx arg mult ret+      }+  | ParsedFunArg+      { span :: SrcSpanAnnA,+        arg :: LocatedA arg,+        doc :: Maybe (LHsDoc GhcPs),+        multAnn :: HsMultAnnOf (LocatedA mult) GhcPs,+        next :: ParsedFunRepr' sig ctx arg mult ret+      }+  | ParsedFunReturn+      { ret :: LocatedA ret,+        doc :: Maybe (LHsDoc GhcPs)+      }++class+  ( Anno (FunReprCtx a) ~ SrcSpanAnnA,+    Outputable (FunReprCtx a)+  ) =>+  FunRepr a+  where+  parseFunRepr :: LocatedA a -> ParsedFunRepr a++  type FunReprSig a+  type FunReprSig a = ()+  renderFunReprSig :: FunReprSig a -> R ()+  default renderFunReprSig :: (FunReprSig a ~ ()) => FunReprSig a -> R ()+  renderFunReprSig () = space *> token'dcolon++  type FunReprCtx a+  type FunReprCtx a = a+  renderFunReprCtx :: FunReprCtx a -> R ()++  type FunReprArg a+  type FunReprArg a = a+  renderFunReprArg :: FunReprArg a -> R ()++  type FunReprMult a+  type FunReprMult a = a+  renderFunReprMult :: FunReprMult a -> R ()++  type FunReprRet a+  type FunReprRet a = a+  renderFunReprRet :: FunReprRet a -> R ()++-- | Some functions rely on HsType having a FunRepr instance, but the instance+-- can't be implemented here, since p_hsType is implemented in Type.hs, which+-- would cause a circular dependency. So we'll add it as an explicit constraint+-- and the call-site should bring the FunRepr instance for HsType in scope.+type FunReprHsType =+  ( FunRepr (HsType GhcPs),+    FunReprSig (HsType GhcPs) ~ (),+    FunReprCtx (HsType GhcPs) ~ HsType GhcPs,+    FunReprArg (HsType GhcPs) ~ HsType GhcPs,+    FunReprMult (HsType GhcPs) ~ HsType GhcPs,+    FunReprRet (HsType GhcPs) ~ HsType GhcPs+  )++hasDocs :: ParsedFunRepr' ig ctx arg mult ret -> Bool+hasDocs = \case+  ParsedFunSig {next} -> hasDocs next+  ParsedFunForall {next} -> hasDocs next+  ParsedFunQuals {next} -> hasDocs next+  ParsedFunArg {doc, next} -> isJust doc || hasDocs next+  ParsedFunReturn {doc} -> isJust doc++{----- Rendering ParsedFunRepr -----}++-- | For implementing function-arrows and related configuration, we'll collect+-- all the components of the function type first, then render as a block instead+-- of rendering each part independently, which will let us track local state+-- within a function type.+--+-- This function should be passed the first function-related construct we find;+-- see FunRepr for more details.+p_hsFun :: forall a. (FunRepr a, FunReprHsType) => a -> R ()+p_hsFun = p_hsFunParsed @a . parseFunRepr . L (noAnn @SrcSpanAnnA)++p_hsFunParsed :: forall a. (FunRepr a, FunReprHsType) => ParsedFunRepr a -> R ()+p_hsFunParsed = runPrintHsFun . p_hsFunParsed' @a++p_hsFunParsed' :: forall a. (FunRepr a, FunReprHsType) => ParsedFunRepr a -> PrintHsFun ()+p_hsFunParsed' = \case+  ParsedFunSig {sig, next} -> do+    -- Should only happen at the very beginning, if at all+    p_parsedFunSig @a sig next+    setMultilineContext next+    p_hsFunParsed' @a next+  ParsedFunForall {tele, next} -> do+    p_parsedFunForall tele+    setMultilineContext next+    p_hsFunParsed' @a next+  ParsedFunQuals {ctxs, next} -> do+    p_parsedFunQuals @a ctxs+    setMultilineContext next+    p_hsFunParsed' @a next+  ParsedFunArg {span, arg, doc, multAnn, next} -> do+    p_parsedFunArg @a span arg doc multAnn+    case next of+      ParsedFunArg {} -> pure ()+      _ -> do+        -- Don't reset multiline context within args; all args should be on+        -- one line together, or all on separate lines+        setMultilineContext next+        -- Reset inArgList, needed if there are multiple arg lists in one+        -- function type (e.g. `a -> Show a => b -> c`)+        setInArgList False+    p_hsFunParsed' @a next+  ParsedFunReturn {ret, doc} -> do+    p_parsedFunReturn @a ret doc++p_parsedFunSig :: forall a. (FunRepr a) => FunReprSig a -> ParsedFunRepr a -> PrintHsFun ()+p_parsedFunSig sig fun = do+  isTrailing <- getIsTrailing+  if isTrailing (Isn't #argDelim)+    then liftR $ do+      renderFunReprSig @a sig+      if hasDocs fun then newline else breakpoint+    else do+      liftR breakpoint+      setLeadingDelim (token'dcolon >> space)++p_parsedFunForall :: (FunReprHsType) => LocatedA (HsForAllTelescope GhcPs) -> PrintHsFun ()+p_parsedFunForall x@(L _ tele) = do+  setLocated_ x+  applyLeadingDelim+  vis <-+    case tele of+      HsForAllInvis _ bndrs -> do+        liftR $ p_forallBndrsStart p_hsTyVarBndr bndrs+        pure ForAllInvis+      HsForAllVis _ bndrs -> do+        liftR $ p_forallBndrsStart p_hsTyVarBndr bndrs+        pure ForAllVis++  isTrailing <- getIsTrailing+  isMultiline <- getIsMultiline+  if isTrailing (Isn't #argDelim) || not isMultiline+    then do+      liftR $ p_forallBndrsEnd (Without #extraSpace) vis+      interArgBreak+    else do+      interArgBreak+      let extraSpace = if isMultiline then With #extraSpace else Without #extraSpace+      setLeadingDelim (p_forallBndrsEnd extraSpace vis)++p_parsedFunQuals :: forall a. (FunRepr a) => [LocatedA (LocatedC [LocatedA (FunReprCtx a)])] -> PrintHsFun ()+p_parsedFunQuals ctxs = do+  isTrailing <- getIsTrailing+  -- we only want to set located on the first context+  case ctxs of+    ctx : _ -> setLocated_ ctx+    _ -> pure ()++  forM_ ctxs $ \(L _ lctx) -> do+    applyLeadingDelim+    liftR $ located lctx (p_hsContext' (renderFunReprCtx @a))+    if isTrailing (Isn't #argDelim)+      then do+        liftR $ space >> token'darrow+        interArgBreak+      else do+        interArgBreak+        setLeadingDelim (token'darrow >> space)++p_parsedFunArg ::+  forall a.+  (FunRepr a) =>+  SrcSpanAnnA ->+  LocatedA (FunReprArg a) ->+  Maybe (LHsDoc GhcPs) ->+  HsMultAnnOf (LocatedA (FunReprMult a)) GhcPs ->+  PrintHsFun ()+p_parsedFunArg span item doc multAnn = do+  isTrailing <- getIsTrailing++  -- We only want to set located on the first arg+  inArgList <- getInArgList+  when (not inArgList) $ do+    setLocated_ (L span item)+    setInArgList True++  let renderArrow = p_arrow (located' (renderFunReprMult @a)) multAnn+  withHaddocks (Isn't #end) doc $ do+    withApplyLeadingDelim $ \applyLeadingDelim' ->+      liftR . located item $ \arg -> do+        traverse_ id applyLeadingDelim'+        renderFunReprArg @a arg+    if isTrailing (Is #argDelim)+      then do+        liftR $ space >> renderArrow+        interArgBreak+      else do+        interArgBreak+        setLeadingDelim (renderArrow >> space)++p_parsedFunReturn ::+  forall a.+  (FunRepr a) =>+  LocatedA (FunReprRet a) ->+  Maybe (LHsDoc GhcPs) ->+  PrintHsFun ()+p_parsedFunReturn item doc = do+  setLocated_ item+  withHaddocks (Is #end) doc $ do+    applyLeadingDelim+    liftR $ located item (renderFunReprRet @a)++{----- forall -----}++data ForAllVisibility = ForAllInvis | ForAllVis++-- | Render several @forall@-ed variables.+p_forallBndrs ::+  (HasLoc l) =>+  ForAllVisibility ->+  (a -> R ()) ->+  [GenLocated l a] ->+  R ()+p_forallBndrs vis p tyvars = do+  p_forallBndrsStart p tyvars+  p_forallBndrsEnd (Without #extraSpace) vis++p_forallBndrsStart :: (HasLoc l) => (a -> R ()) -> [GenLocated l a] -> R ()+p_forallBndrsStart _ [] = token'forall+p_forallBndrsStart p tyvars = do+  switchLayout (locA <$> tyvars) $ do+    token'forall+    breakpoint+    inci $ do+      sitcc $ sep breakpoint (sitcc . located' p) tyvars++p_forallBndrsEnd :: Choice "extraSpace" -> ForAllVisibility -> R ()+p_forallBndrsEnd extraSpace = \case+  ForAllInvis -> txt dot >> space+  ForAllVis -> space >> token'rarrow+  where+    dot = if Choice.isTrue extraSpace then " ." else "."++{----- HsTyVarBndr -----}++class IsTyVarBndrFlag flag where+  isInferred :: flag -> Bool+  p_tyVarBndrFlag :: flag -> R ()+  p_tyVarBndrFlag _ = pure ()++instance IsTyVarBndrFlag () where+  isInferred () = False++instance IsTyVarBndrFlag Specificity where+  isInferred = \case+    InferredSpec -> True+    SpecifiedSpec -> False++instance IsTyVarBndrFlag (HsBndrVis GhcPs) where+  isInferred _ = False+  p_tyVarBndrFlag = \case+    HsBndrRequired NoExtField -> pure ()+    HsBndrInvisible _ -> txt "@"++p_hsTyVarBndr ::+  (IsTyVarBndrFlag flag, FunReprHsType) =>+  HsTyVarBndr flag GhcPs -> R ()+p_hsTyVarBndr HsTvb {..} = do+  p_tyVarBndrFlag tvb_flag+  let wrap+        | isInferred tvb_flag = braces N+        | otherwise = case tvb_kind of+            HsBndrKind {} -> parens N+            HsBndrNoKind {} -> id+  wrap $ do+    case tvb_var of+      HsBndrVar _ x -> p_rdrName x+      HsBndrWildCard _ -> txt "_"+    case tvb_kind of+      HsBndrKind _ k -> inci $ do+        p_hsFunParsed @(HsType GhcPs) $+          ParsedFunSig+            { sig = (),+              next = parseFunRepr k+            }+      HsBndrNoKind _ -> pure ()++{----- Contexts -----}++p_hsContext' ::+  (Outputable (GenLocated (Anno a) a), HasLoc (Anno a)) =>+  (a -> R ()) ->+  [XRec GhcPs a] ->+  R ()+p_hsContext' f = \case+  [] -> txt "()"+  [x] -> located x f+  xs -> do+    shouldSort <- getPrinterOpt poSortConstraints+    let sort = if shouldSort then sortOn showOutputable else id+    parens N $ sep commaDel (sitcc . located' f) (sort xs)++{----- Helpers -----}++withHaddocks :: (MonadR m) => Choice "end" -> Maybe (LHsDoc GhcPs) -> m a -> m a+withHaddocks isEnd doc m = do+  isTrailing <- getIsTrailing+  isLeadingHaddock <-+    liftR $+      getPrinterOpt poHaddockLocSignature <&> \case+        HaddockLocSigAuto ->+          if isTrailing (Is #argDelim) then With #pipe else Without #pipe+        HaddockLocSigLeading ->+          With #pipe+        HaddockLocSigTrailing ->+          Without #pipe++  if Choice.isTrue isLeadingHaddock+    then do+      traverse (liftR . p_hsDocInline Pipe (With #endNewline)) doc *> m+    else do+      let (pre, endNewline) =+            if Choice.isTrue isEnd+              then (when (isJust doc) (liftR newline), Without #endNewline)+              else (pure (), With #endNewline)+      m <* pre <* traverse (liftR . p_hsDocInline Caret endNewline) doc++interArgBreak :: PrintHsFun ()+interArgBreak = do+  isMultiline <- getIsMultiline+  liftR $ if isMultiline then newline else breakpoint
src/Ormolu/Printer/Operators.hs view
@@ -24,8 +24,8 @@ import Ormolu.Utils  -- | Intermediate representation of operator trees, where a branching is not--- just a binary branching (with a left node, right node, and operator like--- in the GHC's AST), but rather a n-ary branching, with n + 1 nodes and n+-- just a binary branching (with a left node, a right node, and an operator,+-- as in the GHC AST), but rather an n-ary branching, with n + 1 nodes and n -- operators (n >= 1). -- -- This representation allows us to put all the operators with the same@@ -49,7 +49,7 @@   { -- | The actual operator     opiOp :: op,     -- | Its name, if available. We use 'Maybe RdrName' here instead of-    -- 'RdrName' because the name-fetching function received by+    -- 'RdrName' because the name-fetching function passed to     -- 'reassociateOpTree' returns a 'Maybe'     opiName :: Maybe RdrName,     -- | Information about the fixity direction and precedence level of the@@ -83,13 +83,14 @@         (Just n1, Just n2) -> n1 == n2         _ -> False --- | Return combined 'SrcSpan's of all elements in this 'OpTree'.+-- | Return the combined 'SrcSpan's of all elements in this 'OpTree'. opTreeLoc :: (HasLoc l) => OpTree (GenLocated l a) b -> SrcSpan opTreeLoc (OpNode n) = getHasLoc n opTreeLoc (OpBranches exprs _) =   combineSrcSpans' . fmap opTreeLoc $ exprs --- | Re-associate an 'OpTree' taking into account precedence of operators.+-- | Re-associate an 'OpTree' taking into account the precedence of+-- operators. -- Users are expected to first construct an initial 'OpTree', then -- re-associate it using this function before printing. reassociateOpTree ::@@ -134,7 +135,7 @@           Nothing -> defaultFixityApproximation           Just rdrName -> inferFixity debug rdrName modFixityMap --- | Given a 'OpTree' of any shape, produce a flat 'OpTree', where every+-- | Given an 'OpTree' of any shape, produce a flat 'OpTree' where every -- node and operator is directly connected to the root. makeFlatOpTree :: OpTree ty op -> OpTree ty op makeFlatOpTree (OpNode n) = OpNode n@@ -151,9 +152,9 @@     interleave [] ys = ys     interleave xs [] = xs --- | Starting from a flat 'OpTree' (i.e. a n-ary tree of depth 1,--- without regard for operator fixities), build an 'OpTree' with proper--- sub-trees (according to the fixity info carried by the nodes).+-- | Starting from a flat 'OpTree' (i.e. an n-ary tree of depth 1, without+-- regard for operator fixities), build an 'OpTree' with proper sub-trees+-- (according to the fixity info carried by the nodes). -- -- We have two complementary ways to build the proper sub-trees: --@@ -182,12 +183,11 @@ --   will become --     [[ex0 op0 ex1 op1 ex2] op2 ex3 op3 [ex4 op4 ex5] op5 ex6 op6 ex7] ----- We will also recursively apply the same logic on every sub-tree built--- during the process. The two principles are not overlapping and thus are--- required, because we are comparing precedence level ranges. In the case--- where we can't find a non-empty set {min,max}Ops with one logic or the--- other, we finally try to split the tree on “hard splitters” if there is--- any.+-- We also recursively apply the same logic to every sub-tree built during+-- the process. The two principles do not overlap, and both are required,+-- because we are comparing precedence level ranges. In the case where we+-- cannot find a non-empty set {min,max}Ops with one approach or the other,+-- we finally try to split the tree on “hard splitters”, if there are any. reassociateFlatOpTree ::   -- | Flat 'OpTree', with fixity info wrapped around each operator   OpTree ty (OpInfo op) ->@@ -203,9 +203,9 @@       indices -> splitTree noptExprs noptOps indices   where     indicesOfHardSplitter =-      fmap fst $-        filter (isHardSplitterOp . opiFixityApproximation . snd) $-          zip [0 ..] noptOps+      fmap fst+        $ filter (isHardSplitterOp . opiFixityApproximation . snd)+        $ zip [0 ..] noptOps     indexOfMinMaxPrecOps [] = (Nothing, Nothing)     indexOfMinMaxPrecOps (oo : oos) = go oos 1 oo (Just [0]) oo (Just [0])       where@@ -276,10 +276,10 @@           OpTree ty (OpInfo op)         go [] _ _ _ subExprs subOps resExprs resOps =           -- No expr left to process.-          -- because we are in a "splitting" logic, there is at least one+          -- Because we are in a "splitting" logic, there is at least one           -- expr in the subExprs bag, so we build a subtree (if necessary)-          -- with sub-bags, add the node/subtree to the result bag, and then-          -- emit the result tree+          -- from the sub-bags, add the node/subtree to the result bag, and+          -- then emit the result tree.           let resExpr = buildFromSub (NE.fromList subExprs) subOps            in OpBranches (NE.reverse (resExpr :| resExprs)) (reverse resOps)         go (x : xs) (o : os) (idx : idxs) i subExprs subOps resExprs resOps@@ -287,13 +287,13 @@               -- The op we are looking at is one on which we need to split.               -- So we build a subtree from the sub-bags and the current               -- expr, append it to the result exprs, and continue with-              -- cleared sub-bags+              -- cleared sub-bags.               let resExpr = buildFromSub (x :| subExprs) subOps                in go xs os idxs (i + 1) [] [] (resExpr : resExprs) (o : resOps)         go (x : xs) ops idxs i subExprs subOps resExprs resOps =           -- Either there is no op left, or the op we are looking at is not           -- one on which we need to split. So we just add both the current-          -- expr and current op (if there is any) to the sub-bags+          -- expr and the current op (if there is any) to the sub-bags.           let (ops', subOps') = moveOneIfPossible ops subOps            in go xs ops' idxs (i + 1) (x : subExprs) subOps' resExprs resOps @@ -324,11 +324,11 @@           -- result tree           OpTree ty (OpInfo op)         go [] _ _ _ subExprs subOps resExprs resOps =-          -- no expr left to process-          -- because we are in a "grouping" logic, the subExprs bag might be-          -- empty. If it is not, we build a subtree (if necessary) with+          -- No expr left to process.+          -- Because we are in a "grouping" logic, the subExprs bag might be+          -- empty. If it is not, we build a subtree (if necessary) from the           -- sub-bags and add the resulting node/subtree to the result bag.-          -- In any case, we then emit the result tree+          -- In any case, we then emit the result tree.           let resExprs' = case NE.nonEmpty subExprs of                 Nothing -> NE.fromList resExprs                 Just subExprs' -> buildFromSub subExprs' subOps :| resExprs@@ -350,8 +350,8 @@         go (x : xs) ops idxs i [] subOps resExprs resOps =           -- Either there is no op left, or the op we are looking at is not           -- one on which we need to split, but the sub-bags are empty. So-          -- we just add both the current expr and current op (if there is-          -- any) to the result bags+          -- we just add both the current expr and the current op (if there+          -- is any) to the result bags.           let (ops', resOps') = moveOneIfPossible ops resOps            in go xs ops' idxs (i + 1) [] subOps (x : resExprs) resOps' @@ -364,9 +364,9 @@       x :| [] -> x       _ -> OpBranches (NE.reverse subExprs) (reverse subOps) --- | Indicate if an operator has @'InfixR' 0@ fixity. We special-case this--- class of operators because they often have, like ('$'), a specific--- “separator” use-case, and we sometimes format them differently than other+-- | Indicate whether an operator has @'InfixR' 0@ fixity. We special-case+-- this class of operators because, like ('$'), they often have a specific+-- “separator” use case, and we sometimes format them differently from other -- operators. isHardSplitterOp :: FixityApproximation -> Bool isHardSplitterOp = (== FixityApproximation (Just InfixR) 0 0)
− src/Ormolu/Printer/SpanStream.hs
@@ -1,49 +0,0 @@--- | Build span stream from AST.-module Ormolu.Printer.SpanStream-  ( SpanStream (..),-    mkSpanStream,-  )-where--import Data.Data (Data)-import Data.Foldable (toList)-import Data.Generics (everything, ext1Q, ext2Q)-import Data.List (sortOn)-import Data.Maybe (maybeToList)-import Data.Sequence (Seq)-import Data.Sequence qualified as Seq-import Data.Typeable (cast)-import GHC.Parser.Annotation-import GHC.Types.SrcLoc---- | A stream of 'RealSrcSpan's in ascending order. This allows us to tell--- e.g. whether there is another \"located\" element of AST between current--- element and comment we're considering for printing.-newtype SpanStream = SpanStream [RealSrcSpan]-  deriving (Eq, Show, Data, Semigroup, Monoid)---- | Create 'SpanStream' from a data structure containing \"located\"--- elements.-mkSpanStream ::-  (Data a) =>-  -- | Data structure to inspect (AST)-  a ->-  SpanStream-mkSpanStream a =-  SpanStream-    . sortOn realSrcSpanStart-    . toList-    $ everything mappend (const mempty `ext2Q` queryLocated `ext1Q` queryEpAnn) a-  where-    queryLocated ::-      (Data e0) =>-      GenLocated e0 e1 ->-      Seq RealSrcSpan-    queryLocated (L mspn _) =-      maybe mempty srcSpanToRealSrcSpanSeq (cast mspn :: Maybe SrcSpan)--    queryEpAnn :: EpAnn ann -> Seq RealSrcSpan-    queryEpAnn = srcSpanToRealSrcSpanSeq . locA--    srcSpanToRealSrcSpanSeq =-      Seq.fromList . maybeToList . srcSpanToRealSrcSpan
src/Ormolu/Processing/Common.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} --- | Common definitions for pre- and post- processing.+-- | Common definitions for pre- and post-processing. module Ormolu.Processing.Common   ( removeIndentation,     reindent,@@ -40,7 +40,7 @@     (_, nonPrefix) = splitAt regionPrefixLength ls     middle = take (length nonPrefix - regionSuffixLength) nonPrefix --- | Convert a set of line indices into disjoint 'RegionDelta's+-- | Convert a set of line indices into disjoint 'RegionDeltas'. intSetToRegions ::   -- | Total number of lines   Int ->
src/Ormolu/Processing/Preprocess.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}@@ -12,6 +13,8 @@ import Data.Array as A import Data.Bifunctor (bimap) import Data.Char (isSpace)+import Data.Choice (Choice)+import Data.Choice qualified as Choice import Data.Function ((&)) import Data.IntMap (IntMap) import Data.IntMap.Strict qualified as IntMap@@ -29,13 +32,14 @@ -- and subregions to be formatted. preprocess ::   -- | Whether CPP is enabled-  Bool ->+  Choice "cppEnabled" ->   RegionDeltas ->   Text ->   [Either Text RegionDeltas] preprocess cppEnabled region rawInput = rawSnippetsAndRegionsToFormat   where-    (linesNotToFormat', replacementLines) = linesNotToFormat cppEnabled region rawInput+    (linesNotToFormat', replacementLines) =+      linesNotToFormat cppEnabled region rawInput     regionsToFormat =       intSetToRegions rawLineLength $         IntSet.fromAscList [1 .. rawLineLength] IntSet.\\ linesNotToFormat'@@ -57,8 +61,8 @@         & dropWhile isBlankRawSnippet         & L.dropWhileEnd isBlankRawSnippet     -- For every formattable region, we want to ensure that it is separated by-    -- a blank line from preceding/succeeding raw snippets if it starts/ends-    -- with a blank line.+    -- a blank line from the preceding/succeeding raw snippets if it+    -- starts/ends with a blank line.     -- Empty formattable regions are replaced by a blank line instead.     -- Extraneous raw snippets at the start/end are dropped afterwards.     patchSeparatingBlankLines = \case@@ -91,7 +95,7 @@ -- for specific lines. linesNotToFormat ::   -- | Whether CPP is enabled-  Bool ->+  Choice "cppEnabled" ->   RegionDeltas ->   Text ->   (IntSet, IntMap Text)@@ -100,13 +104,16 @@   where     unconsidered =       IntSet.fromAscList $-        [1 .. regionPrefixLength] <> [totalLines - regionSuffixLength + 1 .. totalLines]+        [1 .. regionPrefixLength]+          <> [totalLines - regionSuffixLength + 1 .. totalLines]     totalLines = length (T.lines input)     regionLines = linesInRegion region input     (magicDisabled, lineUpdates) = magicDisabledLines regionLines     otherDisabled = mconcat allLines regionLines       where-        allLines = [shebangLines, linePragmaLines] <> [cppLines | cppEnabled]+        allLines =+          [shebangLines, linePragmaLines]+            <> [cppLines | Choice.isTrue cppEnabled]  -- | Ormolu state. data OrmoluState@@ -166,7 +173,7 @@   | Just rest <- isMagicComment "FOURMOLU_DISABLE" s = Just $ "{- FOURMOLU_DISABLE -}" <> rest   | otherwise = Nothing --- | Construct a function for whitespace-insensitive matching of string.+-- | Construct a function for whitespace-insensitive matching of a string. isMagicComment ::   -- | What to expect   Text ->
src/Ormolu/Terminal.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} --- | An abstraction for colorful output in terminal.+-- | An abstraction for colorful output in the terminal. module Ormolu.Terminal   ( -- * The 'Term' abstraction     Term,@@ -56,7 +56,7 @@ data ColorMode = Never | Always | Auto   deriving (Eq, Show, Enum, Bounded) --- | Run 'Term' monad.+-- | Run the 'Term' monad. runTerm ::   Term ->   -- | Color mode
src/Ormolu/Utils.hs view
@@ -8,6 +8,7 @@     combineSrcSpans',     notImplemented,     showOutputable,+    containsHaddocks,     splitDocString,     incSpanLine,     separatedByBlank,@@ -19,6 +20,8 @@   ) where +import Data.Data (Data)+import Data.Generics.Schemes (listify) import Data.List (dropWhileEnd) import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE@@ -48,7 +51,7 @@   | LastPos   deriving (Eq, Show) --- | Attach 'RelativePos'es to elements of a given list.+-- | Attach 'RelativePos'es to the elements of the given list. attachRelativePos :: [a] -> [(RelativePos, a)] attachRelativePos = \case   [] -> []@@ -67,27 +70,37 @@ notImplemented :: String -> a notImplemented msg = error $ "not implemented yet: " ++ msg --- | Pretty-print an 'GHC.Outputable' thing.+-- | Pretty-print a 'GHC.Outputable' thing. showOutputable :: (Outputable o) => o -> String showOutputable = showSDoc baseDynFlags . ppr +-- | Does this fragment of the syntax tree carry a Haddock anywhere inside+-- it?+--+-- Unlike the span of a Haddock, which sits wherever the author wrote it,+-- this answers the question the printer actually has: will rendering this+-- fragment emit documentation?+containsHaddocks :: (Data a) => a -> Bool+containsHaddocks =+  not . null . listify (const True :: HsDocString -> Bool)+ -- | Split and normalize a doc string. The result is a list of lines that -- make up the comment.-splitDocString :: Bool -> HsDocString -> [Text]-splitDocString shouldEscapeCommentBraces docStr =+splitDocString :: HsDocString -> [Text]+splitDocString docStr =   case r of     [] -> [""]     _ -> r   where     r =-      fmap (escapeLeadingDollar . escapeCommentBraces)+      fmap escapeLeadingDollar         . dropPaddingSpace'         . dropWhileEnd T.null         . fmap (T.stripEnd . T.pack)         . lines         $ renderHsDocString docStr-    -- We cannot have the first character to be a dollar because in that-    -- case it'll be a parse error (apparently collides with named docs+    -- We cannot let the first character be a dollar, because in that case+    -- it would be a parse error (apparently it collides with the named docs     -- syntax @-- $name@ somehow).     escapeLeadingDollar txt =       case T.uncons txt of@@ -117,12 +130,8 @@            in if leadingSpace x                 then dropSpace <$> xs                 else xs-    escapeCommentBraces =-      if shouldEscapeCommentBraces-        then T.replace "{-" "{\\-" . T.replace "-}" "-\\}"-        else id --- | Increment line number in a 'SrcSpan'.+-- | Increment the line number in a 'SrcSpan'. incSpanLine :: Int -> SrcSpan -> SrcSpan incSpanLine i = \case   RealSrcSpan s _ ->@@ -148,7 +157,8 @@ separatedByBlankNE :: (a -> SrcSpan) -> NonEmpty a -> NonEmpty a -> Bool separatedByBlankNE loc a b = separatedByBlank loc (NE.last a) (NE.head b) --- | Return 'True' if one span ends on the same line the second one starts.+-- | Return 'True' if one span ends on the same line where the second one+-- starts. onTheSameLine :: SrcSpan -> SrcSpan -> Bool onTheSameLine a b =   isOneLineSpan (mkSrcSpan (srcSpanEnd a) (srcSpanStart b))@@ -169,7 +179,7 @@   buf <- mallocPlainForeignPtrBytes (len + 3)   withForeignPtr buf $ \ptr -> do     TFFI.unsafeCopyToPtr txt ptr-    -- last three bytes have to be zero for easier decoding+    -- The last three bytes have to be zero for easier decoding.     pokeElemOff ptr len 0     pokeElemOff ptr (len + 1) 0     pokeElemOff ptr (len + 2) 0
src/Ormolu/Utils/Fixity.hs view
@@ -24,9 +24,9 @@ import Text.Megaparsec (errorBundlePretty)  -- | Attempt to locate and parse an @.ormolu@ file. If it does not exist,--- default fixity map and module reexports are returned. This function--- maintains a cache of fixity overrides and module re-exports where cabal--- file paths act as keys.+-- the default fixity map and module re-exports are returned. This function+-- maintains a cache of fixity overrides and module re-exports keyed by+-- @.ormolu@ file path. getDotOrmoluForSourceFile ::   (MonadIO m) =>   -- | 'CabalInfo' already obtained for this source file@@ -69,8 +69,8 @@ parseFixityDeclarationStr =   first errorBundlePretty . parseFixityDeclaration . T.pack --- | A wrapper around 'parseModuleReexportDeclaration' for parsing--- a individual module reexport.+-- | A wrapper around 'parseModuleReexportDeclaration' for parsing an+-- individual module re-export. parseModuleReexportDeclarationStr ::   -- | Input to parse   String ->
src/Ormolu/Utils/Glob.hs view
@@ -1,6 +1,7 @@ module Ormolu.Utils.Glob   ( Glob,     mkGlob,+    matchAllGlob,     matchesGlob,   ) where@@ -31,6 +32,9 @@       t ->         let (m, t') = break (== '*') t          in MatchExactly m : parsePart t'++matchAllGlob :: Glob+matchAllGlob = Glob [DoubleWildcard]  matchesGlob :: String -> Glob -> Bool matchesGlob s (Glob ps) = s `matchesGlobParts` ps
src/Ormolu/Utils/IO.hs view
@@ -16,7 +16,7 @@ import Data.Map.Lazy qualified as M import System.Directory import System.FilePath-import System.IO.Error (isDoesNotExistError)+import System.IO.Error (isDoesNotExistError, isPermissionError)  -- | Find the path to the closest file higher in the file hierarchy that -- satisfies a given predicate.@@ -30,25 +30,36 @@   m (Maybe FilePath) findClosestFileSatisfying isRightFile rootOfSearch = liftIO $ do   parentDir <- takeDirectory <$> makeAbsolute rootOfSearch-  dirEntries <--    listDirectory parentDir `catch` \case-      (isDoesNotExistError -> True) -> pure []+  maybeDirEntries <-+    (Just <$> listDirectory parentDir) `catch` \case+      -- The directory does not exist. This is expected: the search may start+      -- from a path that does not exist yet (e.g. a file about to be created),+      -- whose absolute form still lies below existing parent directories.+      -- Treat it as empty and keep searching upwards.+      (isDoesNotExistError -> True) -> pure (Just [])+      -- We lack the permissions to read the directory, e.g. when running in a+      -- sandbox that restricts access to parent directories. Abort the search:+      -- we almost certainly cannot read any parent directory either.+      (isPermissionError -> True) -> pure Nothing       e -> throwIO e-  let searchAtParentDirLevel = \case-        [] -> pure Nothing-        x : xs ->-          if isRightFile x-            then-              doesFileExist (parentDir </> x) >>= \case-                True -> pure (Just x)-                False -> searchAtParentDirLevel xs-            else searchAtParentDirLevel xs-  searchAtParentDirLevel dirEntries >>= \case-    Just foundFile -> pure . Just $ parentDir </> foundFile-    Nothing ->-      if isDrive parentDir-        then pure Nothing-        else findClosestFileSatisfying isRightFile parentDir+  case maybeDirEntries of+    Nothing -> pure Nothing+    Just entries -> do+      let searchAtParentDirLevel = \case+            [] -> pure Nothing+            x : xs ->+              if isRightFile x+                then+                  doesFileExist (parentDir </> x) >>= \case+                    True -> pure (Just x)+                    False -> searchAtParentDirLevel xs+                else searchAtParentDirLevel xs+      searchAtParentDirLevel entries >>= \case+        Just foundFile -> pure . Just $ parentDir </> foundFile+        Nothing ->+          if isDrive parentDir+            then pure Nothing+            else findClosestFileSatisfying isRightFile parentDir  newtype Cache k v = Cache (IORef (Map k v)) 
+ tests/Ormolu/Comments/AnchorSpec.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Tests for the containment tree and the positional attachment rules.+module Ormolu.Comments.AnchorSpec (spec) where++import Data.List.NonEmpty (NonEmpty (..))+import GHC.Data.FastString (fsLit)+import GHC.Types.SrcLoc+import Ormolu.Comments.Anchor+import Ormolu.Comments.Tree+import Ormolu.Parser.CommentStream+import Test.Hspec++spec :: Spec+spec = do+  describe "mkSpanForest" $ do+    it "nests spans by containment" $+      mkSpanForest [spn 1 1 9 9, spn 2 1 3 9, spn 2 3 2 8]+        `shouldBe` [ SpanTree+                       (spn 1 1 9 9)+                       [SpanTree (spn 2 1 3 9) [SpanTree (spn 2 3 2 8) []]]+                   ]+    it "keeps siblings in ascending order" $+      fmap stSpan (mkSpanForest [spn 5 1 5 9, spn 1 1 1 9, spn 3 1 3 9])+        `shouldBe` [spn 1 1 1 9, spn 3 1 3 9, spn 5 1 5 9]+    it "treats a repeated span as one element" $+      -- Several AST nodes routinely share a span; the comment can only be+      -- owned once.+      countNodes (mkSpanForest [spn 1 1 9 9, spn 1 1 9 9, spn 1 1 9 9])+        `shouldBe` 1+    it "drops spans that overlap without being contained" $+      countNodes (mkSpanForest [spn 1 1 5 9, spn 3 1 7 9]) `shouldBe` 1+    it "keeps zero-width spans" $+      -- The printer enters a zero-width span at each end of an empty list,+      -- deliberately, so that a comment written inside the brackets has+      -- something to attach to.+      mkSpanForest [spn 1 1 1 1, spn 2 1 2 9]+        `shouldBe` [SpanTree (spn 1 1 1 1) [], SpanTree (spn 2 1 2 9) []]++  describe "anchorFor" $ do+    let forest = mkSpanForest [block, stmt1, stmt2]+        block = spn 1 1 5 10+        stmt1 = spn 2 3 2 9+        stmt2 = spn 4 3 4 9++    it "puts a comment between two elements before the later one" $+      anchorFor forest (ownLine 3 3 3 12) `shouldBe` AnchorBefore stmt2+    it "attaches a comment that trails code to the element it trails" $+      anchorFor forest (trailing 2 12 2 20) `shouldBe` AnchorTrailing stmt1+    it "does not treat a comment as trailing when no code precedes it" $+      -- Same line as the end of stmt1, but alone on its line, so it belongs+      -- to what comes after.+      anchorFor forest (ownLine 2 12 2 20) `shouldBe` AnchorBefore stmt2+    it "attaches a comment after the last element to that element" $+      anchorFor forest (ownLine 5 3 5 9) `shouldBe` AnchorTrailing stmt2+    it "gives a comment inside a childless element to that element" $+      anchorFor (mkSpanForest [spn 1 1 3 3]) (ownLine 2 3 2 9)+        `shouldBe` AnchorInside (spn 1 1 3 3)+    it "leaves a comment outside everything to the module" $+      -- Not trailing the last top-level element: there is nothing it could+      -- trail without being rendered before syntax that preceded it.+      anchorFor forest (ownLine 9 1 9 9) `shouldBe` AnchorModule+    it "leaves a comment to the module when there are no elements at all" $+      anchorFor [] (ownLine 1 1 1 9) `shouldBe` AnchorModule++    -- @f x = -- c@ re-parsed: the comment now falls inside the right-hand+    -- side, which opened on that line and has nothing of its own before the+    -- comment. Without looking one level up it would move onto its own+    -- line, and formatting would not be idempotent.+    it "attaches a comment inside an element that opened on its line to the code before it" $+      let rhs = spn 2 11 3 9+          body = spn 3 3 3 9+       in anchorFor+            (mkSpanForest [block, stmt1, rhs, body])+            (trailing 2 14 2 20)+            `shouldBe` AnchorTrailing stmt1+    it "still lets an element starting on the comment's line lead it" $+      -- The @{-a-}@ of @x = ({-a-} b, c)@ belongs to @b@, not to the @x@+      -- one level up.+      let tuple = spn 2 11 2 30+          b = spn 2 18 2 19+       in anchorFor+            (mkSpanForest [block, stmt1, tuple, b])+            (trailing 2 12 2 17)+            `shouldBe` AnchorBefore b+    it "does not carry a comment out of a list of items" $+      -- @xs ++ [ -- why?@: the comment introduces the items, so carrying it+      -- up to trail @xs@ would drag it out of the brackets.+      let list = spn 2 11 4 9+          itemA = spn 3 3 3 9+          itemB = spn 4 3 4 9+       in anchorFor+            (mkSpanForest [block, stmt1, list, itemA, itemB])+            (trailing 2 14 2 20)+            `shouldBe` AnchorBefore itemA+    it "does not carry a block comment up a level" $+      -- A block comment renders where it stands, so trailing an element one+      -- level up would push it ahead of the tokens that opened the element+      -- it was written inside.+      let rhs = spn 2 11 3 9+          body = spn 3 3 3 9+       in anchorFor+            (mkSpanForest [block, stmt1, rhs, body])+            (blockTrailing 2 14 2 20)+            `shouldBe` AnchorBefore body++    it "does not depend on the order the elements are given in" $+      -- This is the whole point: reordering imports or reassociating an+      -- operator tree must not change who owns a comment.+      anchorFor (mkSpanForest [stmt2, block, stmt1]) (ownLine 3 3 3 12)+        `shouldBe` AnchorBefore stmt2++  describe "attachComments" $+    it "attaches every comment exactly once" $ do+      let comments = [ownLine 3 3 3 12, trailing 4 12 4 20]+          anchors = attachComments comments [spn 1 1 5 10, spn 2 3 2 9, spn 4 3 4 9]+      length anchors `shouldBe` 2+      fmap snd anchors+        `shouldBe` [AnchorBefore (spn 4 3 4 9), AnchorTrailing (spn 4 3 4 9)]++----------------------------------------------------------------------------+-- Helpers++spn :: Int -> Int -> Int -> Int -> RealSrcSpan+spn l1 c1 l2 c2 =+  mkRealSrcSpan+    (mkRealSrcLoc (fsLit "<test>") l1 c1)+    (mkRealSrcLoc (fsLit "<test>") l2 c2)++-- | A comment with code in front of it on the same line.+trailing :: Int -> Int -> Int -> Int -> LComment+trailing l1 c1 l2 c2 = L (spn l1 c1 l2 c2) (Comment True ("-- x" :| []))++-- | A block comment with code in front of it on the same line.+blockTrailing :: Int -> Int -> Int -> Int -> LComment+blockTrailing l1 c1 l2 c2 = L (spn l1 c1 l2 c2) (Comment True ("{- x -}" :| []))++-- | A comment that is alone on its line.+ownLine :: Int -> Int -> Int -> Int -> LComment+ownLine l1 c1 l2 c2 = L (spn l1 c1 l2 c2) (Comment False ("-- x" :| []))
tests/Ormolu/Config/PrinterOptsSpec.hs view
@@ -13,6 +13,7 @@ import Control.Monad (forM_, when) import Data.Algorithm.DiffContext qualified as Diff import Data.Char (isSpace)+import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NonEmpty import Data.Maybe (isJust) import Data.Set qualified as S@@ -26,7 +27,7 @@ import Ormolu.Config import Ormolu.Exception (OrmoluException, printOrmoluException) import Ormolu.Terminal (runTerm)-import Ormolu.Utils.Glob (mkGlob)+import Ormolu.Utils.Glob (matchAllGlob, mkGlob) import Path   ( File,     Path,@@ -111,6 +112,24 @@           checkIdempotence = True         },       TestGroup+        { label = "record-style",+          isMulti = False,+          testCases = liftA2 (,) allOptions allOptions,+          updateConfig = \(recordStyle, commaStyle) opts ->+            opts+              { poRecordStyle = pure recordStyle,+                -- Test record style in combination with comma style+                poCommaStyle = pure commaStyle,+                -- Makes the indentation more visible+                poIndentation = pure 4+              },+          showTestCase = \(recordStyle, commaStyle) ->+            [ renderPrinterOpt recordStyle,+              renderPrinterOpt commaStyle+            ],+          checkIdempotence = True+        },+      TestGroup         { label = "import-export",           isMulti = True,           testCases = allOptions,@@ -130,62 +149,7 @@               ImportGroupByQualified,               ImportGroupByScope,               ImportGroupByScopeThenQualified,-              ImportGroupCustom . NonEmpty.fromList $-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Data.Text"),-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    },-                  ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchAllModules,-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = ImportRulePriority 100-                              }-                          ]-                    },-                  ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "SomeInternal.**"),-                                igrQualifiedMatcher = MatchQualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              },-                            ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Unknown.**"),-                                igrQualifiedMatcher = MatchUnqualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    },-                  ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchLocalModules,-                                igrQualifiedMatcher = MatchUnqualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              },-                            ImportGroupRule-                              { igrModuleMatcher = MatchAllModules,-                                igrQualifiedMatcher = MatchQualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    }-                ]+              ImportGroupCustom importGroupCustomRules             ],           updateConfig = \igs opts ->             opts@@ -446,7 +410,7 @@         Nothing ->           expectationFailure "Output does not exist. Try running with ORMOLU_REGENERATE_EXAMPLES=1"         Just expected ->-          when (actual /= expected) $+          when (actual /= expected) $ do             expectationFailure . T.unpack $               getDiff ("actual", actual) ("expected", expected) @@ -496,9 +460,9 @@  getDiff :: (String, Text) -> (String, Text) -> Text getDiff (s1Name, s1) (s2Name, s2) =-  T.pack . Doc.render $-    Diff.prettyContextDiff (Doc.text s1Name) (Doc.text s2Name) (Doc.text . T.unpack . Diff.unnumber) $-      Diff.getContextDiff (Just 2) (T.lines s1) (T.lines s2)+  T.pack . Doc.render+    $ Diff.prettyContextDiff (Doc.text s1Name) (Doc.text s2Name) (Doc.text . T.unpack . Diff.unnumber)+    $ Diff.getContextDiff (Just 2) (T.lines s1) (T.lines s2)  renderOrmoluException :: OrmoluException -> IO String renderOrmoluException e =@@ -525,3 +489,58 @@ spanEnd f xs =   let xs' = reverse xs    in (reverse $ dropWhile f xs', reverse $ takeWhile f xs')++importGroupCustomRules :: NonEmpty ImportGroup+importGroupCustomRules =+  NonEmpty.fromList+    [ defaultImportGroup . NonEmpty.fromList $+        [ defaultImportGroupRule+            { igrImportListMatcher = MatchWholeModuleImport,+              igrQualifiedMatcher = MatchUnqualifiedOnly+            }+        ],+      defaultImportGroup . NonEmpty.fromList $+        [ defaultImportGroupRule {igrGlob = mkGlob "Data.Text"}+        ],+      defaultImportGroup . NonEmpty.fromList $+        [ defaultImportGroupRule+            { igrPriority = ImportRulePriority 100+            }+        ],+      defaultImportGroup . NonEmpty.fromList $+        [ defaultImportGroupRule+            { igrGlob = mkGlob "SomeInternal.**",+              igrQualifiedMatcher = MatchQualifiedOnly+            },+          defaultImportGroupRule+            { igrGlob = mkGlob "Unknown.**",+              igrQualifiedMatcher = MatchUnqualifiedOnly+            }+        ],+      defaultImportGroup . NonEmpty.fromList $+        [ defaultImportGroupRule+            { igrQualifiedMatcher = MatchUnqualifiedOnly,+              igrScopeMatcher = MatchLocalModules+            },+          defaultImportGroupRule+            { igrQualifiedMatcher = MatchQualifiedOnly+            }+        ]+    ]+  where+    defaultImportGroup :: NonEmpty ImportGroupRule -> ImportGroup+    defaultImportGroup rules =+      ImportGroup+        { igName = Nothing,+          igRules = rules+        }++    defaultImportGroupRule :: ImportGroupRule+    defaultImportGroupRule =+      ImportGroupRule+        { igrGlob = matchAllGlob,+          igrImportListMatcher = MatchAnyImportDeclaration,+          igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+          igrScopeMatcher = MatchAllModules,+          igrPriority = defaultImportRulePriority+        }
tests/Ormolu/ConfigSpec.hs view
@@ -3,14 +3,16 @@ -- | Tests for Fourmolu file configuration. module Ormolu.ConfigSpec (spec) where +import Control.Monad ((>=>)) import Data.ByteString.Char8 qualified as Char8 import Data.List (isInfixOf)+import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NonEmpty import Data.Map qualified as Map import Data.Yaml qualified as Yaml-import Ormolu.Config (FourmoluConfig (..), ImportGroup (..), ImportGroupRule (..), ImportGrouping (..), ImportModuleMatcher (..), ImportRulePriority (ImportRulePriority), PrinterOpts (..), QualifiedImportMatcher (MatchBothQualifiedAndUnqualified, MatchQualifiedOnly, MatchUnqualifiedOnly), defaultImportRulePriority, matchAllRulePriority, resolvePrinterOpts)+import Ormolu.Config import Ormolu.Fixity (ModuleReexports (..))-import Ormolu.Utils.Glob (mkGlob)+import Ormolu.Utils.Glob (matchAllGlob, mkGlob) import Test.Hspec  spec :: Spec@@ -71,165 +73,84 @@             [ "import-grouping:",               "  - name: Some name",               "    rules:",-              "      - match: all"+              "      - {}"             ]         let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)             expectedRules =               NonEmpty.fromList                 [ ImportGroup                     { igName = Just "Some name",-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchAllModules,-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = matchAllRulePriority-                              }-                          ]-                    }-                ]-        actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)-      it "enables the 'qualified' rule option" $ do-        config <--          Yaml.decodeThrow . Char8.pack . unlines $-            [ "import-grouping:",-              "  - rules:",-              "      - glob: \"**\"",-              "        qualified: yes"-            ]-        let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)-            expectedRules =-              NonEmpty.fromList-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "**"),-                                igrQualifiedMatcher = MatchQualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    }-                ]-        actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)-      it "disables the 'qualified' rule option" $ do-        config <--          Yaml.decodeThrow . Char8.pack . unlines $-            [ "import-grouping:",-              "  - rules:",-              "      - glob: \"**\"",-              "        qualified: no"-            ]-        let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)-            expectedRules =-              NonEmpty.fromList-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "**"),-                                igrQualifiedMatcher = MatchUnqualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    }-                ]-        actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)-      it "decodes the 'priority' rule option" $ do-        config <--          Yaml.decodeThrow . Char8.pack . unlines $-            [ "import-grouping:",-              "  - rules:",-              "      - match: all",-              "        priority: 55"-            ]-        let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)-            expectedRules =-              NonEmpty.fromList-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchAllModules,-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = ImportRulePriority 55-                              }-                          ]-                    }-                ]-        actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)-      it "parses a 'glob' rule" $ do-        config <--          Yaml.decodeThrow . Char8.pack . unlines $-            [ "import-grouping:",-              "  - rules:",-              "      - glob: Data.Text.**"-            ]-        let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)-            expectedRules =-              NonEmpty.fromList-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Data.Text.**"),-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    }-                ]-        actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)-      it "parses a 'match' rule for local modules" $ do-        config <--          Yaml.decodeThrow . Char8.pack . unlines $-            [ "import-grouping:",-              "  - rules:",-              "      - match: local-modules"-            ]-        let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)-            expectedRules =-              NonEmpty.fromList-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchLocalModules,-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = defaultImportRulePriority-                              }-                          ]-                    }-                ]-        actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)-      it "parses a 'match' rule for all modules" $ do-        config <--          Yaml.decodeThrow . Char8.pack . unlines $-            [ "import-grouping:",-              "  - rules:",-              "      - match: all"-            ]-        let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)-            expectedRules =-              NonEmpty.fromList-                [ ImportGroup-                    { igName = Nothing,-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchAllModules,-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = matchAllRulePriority-                              }-                          ]+                      igRules = NonEmpty.fromList [defaultImportGroupRule]                     }                 ]         actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)++      let checkRule :: (HasCallStack) => String -> String -> ImportGroupRule -> SpecWith ()+          checkRule desc yamlRuleConfig expectedRuleConfig =+            it ("parses " ++ desc) $ do+              let yamlConfig =+                    unlines+                      [ "import-grouping:",+                        "  - rules:",+                        "      - " ++ yamlRuleConfig+                      ]++                  downCustomRules (ImportGroupCustom customRules) = Just customRules+                  downCustomRules _ = Nothing+                  downSingleton (a :| []) = Just a+                  downSingleton _ = Nothing+                  accessTestedRule = downCustomRules >=> downSingleton >=> downSingleton . igRules++              config <- Yaml.decodeThrow (Char8.pack yamlConfig)++              let actualStrategy = poImportGrouping (cfgFilePrinterOpts config)+              case actualStrategy >>= accessTestedRule of+                Nothing -> expectationFailure "A single tested rule change was expected"+                Just actualRule -> actualRule `shouldBe` expectedRuleConfig++          checkRuleAttribute :: (HasCallStack) => String -> String -> (ImportGroupRule -> ImportGroupRule) -> SpecWith ()+          checkRuleAttribute desc yamlRuleConfig applyExpectedChange = do+            let baseArbitraryYamlConfig = "glob: \"**\""+                yamlConfig = "{ " ++ baseArbitraryYamlConfig ++ ", " ++ yamlRuleConfig ++ "}"++                baseArbitraryConfig = defaultImportGroupRule {igrGlob = matchAllGlob}+                expectedRuleConfig = applyExpectedChange baseArbitraryConfig+            checkRule+              ("rules with the " ++ desc ++ " rule option")+              yamlConfig+              expectedRuleConfig++      checkRule "the 'glob' rule" "glob: Data.Text.**" defaultImportGroupRule {igrGlob = mkGlob "Data.Text.**"}+      checkRule "the default 'glob' rule (match all modules)" "{}" defaultImportGroupRule {igrGlob = mkGlob "**"}++      checkRuleAttribute "'any' import list" "import-list: any" $ \config -> config {igrImportListMatcher = MatchAnyImportDeclaration}+      checkRuleAttribute "'explicit' import list" "import-list: explicit" $ \config -> config {igrImportListMatcher = MatchExplicitImportList}+      checkRuleAttribute "'hiding' import list" "import-list: hiding" $ \config -> config {igrImportListMatcher = MatchHidingImportClause}+      checkRuleAttribute "'none' import list" "import-list: none" $ \config -> config {igrImportListMatcher = MatchWholeModuleImport}+      checkRuleAttribute "default import list" "" $ \config -> config {igrImportListMatcher = MatchAnyImportDeclaration}++      checkRuleAttribute "'qualified: yes'" "qualified: yes" $ \config -> config {igrQualifiedMatcher = MatchQualifiedOnly}+      checkRuleAttribute "'qualified: no'" "qualified: no" $ \config -> config {igrQualifiedMatcher = MatchUnqualifiedOnly}+      checkRuleAttribute "default 'qualified' (match any)" "" $ \config -> config {igrQualifiedMatcher = MatchBothQualifiedAndUnqualified}++      checkRuleAttribute "'scope: any'" "scope: any" $ \config -> config {igrScopeMatcher = MatchAllModules}+      checkRuleAttribute "'scope: local'" "scope: local" $ \config -> config {igrScopeMatcher = MatchLocalModules}+      checkRuleAttribute "'scope: external'" "scope: external" $ \config -> config {igrScopeMatcher = MatchExternalModules}+      checkRuleAttribute "default 'scope' (match all)" "" $ \config -> config {igrScopeMatcher = MatchAllModules}++      checkRuleAttribute "'priority' (55 priority example)" "priority: 55" $ \config -> config {igrPriority = ImportRulePriority 55}+      checkRuleAttribute "'priority' (80 priority example)" "priority: 80" $ \config -> config {igrPriority = ImportRulePriority 80}+      checkRuleAttribute "default 'priority' (50)" "" $ \config -> config {igrPriority = ImportRulePriority 50}++      checkRule+        "the legacy 'match' rule for all modules (with a 100 priority)"+        "match: all"+        defaultImportGroupRule {igrGlob = mkGlob "**", igrScopeMatcher = MatchAllModules, igrPriority = ImportRulePriority 100}+      checkRule+        "the legacy 'match' rule for local modules"+        "match: local-modules"+        defaultImportGroupRule {igrGlob = mkGlob "**", igrScopeMatcher = MatchLocalModules, igrPriority = ImportRulePriority 60}+       it "parses multiple group configurations and rules in their order of appearance in the configuration" $ do         config <-           Yaml.decodeThrow . Char8.pack . unlines $@@ -240,7 +161,6 @@               "  - name: The rest",               "    rules:",               "      - match: all",-              "        priority: 100",               "  - name: My internals and monads unqualified",               "    rules:",               "      - match: local-modules",@@ -262,68 +182,31 @@               NonEmpty.fromList                 [ ImportGroup                     { igName = Just "Text modules",-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Data.Text"),-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = defaultImportRulePriority-                              }-                          ]+                      igRules = NonEmpty.fromList [defaultImportGroupRule {igrGlob = mkGlob "Data.Text"}]                     },                   ImportGroup                     { igName = Just "The rest",-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchAllModules,-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = ImportRulePriority 100-                              }-                          ]+                      igRules = NonEmpty.fromList [defaultImportGroupRule {igrGlob = matchAllGlob, igrPriority = ImportRulePriority 100}]                     },                   ImportGroup                     { igName = Just "My internals and monads unqualified",                       igRules =                         NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchLocalModules,-                                igrQualifiedMatcher = MatchUnqualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              },-                            ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Control.Monad"),-                                igrQualifiedMatcher = MatchUnqualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              }+                          [ defaultImportGroupRule {igrQualifiedMatcher = MatchUnqualifiedOnly, igrScopeMatcher = MatchLocalModules, igrPriority = ImportRulePriority 60},+                            defaultImportGroupRule {igrGlob = mkGlob "Control.Monad", igrQualifiedMatcher = MatchUnqualifiedOnly}                           ]                     },                   ImportGroup                     { igName = Just "My internals and monads qualified",                       igRules =                         NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchLocalModules,-                                igrQualifiedMatcher = MatchQualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              },-                            ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Control.Monad"),-                                igrQualifiedMatcher = MatchQualifiedOnly,-                                igrPriority = defaultImportRulePriority-                              }+                          [ defaultImportGroupRule {igrQualifiedMatcher = MatchQualifiedOnly, igrScopeMatcher = MatchLocalModules, igrPriority = ImportRulePriority 60},+                            defaultImportGroupRule {igrGlob = mkGlob "Control.Monad", igrQualifiedMatcher = MatchQualifiedOnly}                           ]                     },                   ImportGroup                     { igName = Just "Specific monads",-                      igRules =-                        NonEmpty.fromList-                          [ ImportGroupRule-                              { igrModuleMatcher = MatchGlob (mkGlob "Control.Monad.**"),-                                igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,-                                igrPriority = defaultImportRulePriority-                              }-                          ]+                      igRules = NonEmpty.fromList [defaultImportGroupRule {igrGlob = mkGlob "Control.Monad.**"}]                     }                 ]         actualStrategy `shouldBe` Just (ImportGroupCustom expectedRules)@@ -332,9 +215,19 @@               Yaml.decodeEither' @FourmoluConfig . Char8.pack . unlines $                 [ "import-grouping:",                   "  - rules:",-                  "      - some-unknown-rule-type: whatever"+                  "      - match: whatever"                 ]             isAnUnknownModuleMatcher e = case e of-              Left (Yaml.AesonException msg) -> "Unknown or invalid module matcher" `isInfixOf` msg+              Left (Yaml.AesonException msg) -> "Unknown legacy match value" `isInfixOf` msg               _ -> False         decodeResult `shouldSatisfy` isAnUnknownModuleMatcher++defaultImportGroupRule :: ImportGroupRule+defaultImportGroupRule =+  ImportGroupRule+    { igrGlob = matchAllGlob,+      igrImportListMatcher = MatchAnyImportDeclaration,+      igrQualifiedMatcher = MatchBothQualifiedAndUnqualified,+      igrScopeMatcher = MatchAllModules,+      igrPriority = defaultImportRulePriority+    }
tests/Ormolu/Fixity/ParserSpec.hs view
@@ -214,7 +214,7 @@                 elabel "module name"               ]           )-    it "fails with correct parse error (typo: export intead exports)" $+    it "fails with correct parse error (typo: export instead exports)" $       parseModuleReexportDeclaration "module Control.Lens export Control.Lens.Lens"         `shouldFailWith` err           20
tests/Ormolu/FixitySpec.hs view
@@ -201,44 +201,32 @@       ["esqueleto"]       [package_ "bob" $ import_ "Database.Esqueleto.Experimental"]       [(unqual "++.", defaultFixityApproximation)]-  it "default module re-exports: Control.Lens brings into scope Control.Lens.Lens" $+  it "re-exports baked into the database: Control.Lens brings <+~ into scope" $     checkFixities       ["lens"]-      ( applyModuleReexports-          defaultModuleReexports-          [import_ "Control.Lens"]-      )+      [import_ "Control.Lens"]       [(unqual "<+~", FixityApproximation (Just InfixR) 4 4)]-  it "default module re-exports: Control.Lens qualified brings into scope Control.Lens.Lens" $+  it "re-exports baked into the database: Control.Lens qualified" $     checkFixities       ["lens"]-      ( applyModuleReexports-          defaultModuleReexports-          [import_ "Control.Lens" & qualified_]-      )+      [import_ "Control.Lens" & qualified_]       [ (unqual "<+~", defaultFixityApproximation),-        (qual "Control.Lens.Lens" "<+~", defaultFixityApproximation),         (qual "Control.Lens" "<+~", FixityApproximation (Just InfixR) 4 4)       ]-  it "default module re-exports: Control.Lens qualified as brings into scope Control.Lens.Lens" $+  it "re-exports baked into the database: Control.Lens qualified as" $     checkFixities       ["lens"]-      ( applyModuleReexports-          defaultModuleReexports-          [import_ "Control.Lens" & qualified_ & as_ "L"]-      )+      [import_ "Control.Lens" & qualified_ & as_ "L"]       [ (unqual "<+~", defaultFixityApproximation),-        (qual "Control.Lens.Lens" "<+~", defaultFixityApproximation),         (qual "Control.Lens" "<+~", defaultFixityApproximation),         (qual "L" "<+~", FixityApproximation (Just InfixR) 4 4)       ]   it "re-export chains: exported module can itself re-export another module" $ do     let reexports =           ModuleReexports $-            Map.insert+            Map.singleton               "Foo"               ((Nothing, "Control.Lens") :| [])-              (unModuleReexports defaultModuleReexports)     checkFixities       ["lens"]       ( applyModuleReexports@@ -297,7 +285,7 @@       fimportList = Nothing     } --- | Adds an alias for an import.+-- | Add an alias for an import. as_ :: ModuleName -> FixityImport -> FixityImport as_ moduleName fixityImport =   fixityImport
tests/Ormolu/Integration/Utils.hs view
@@ -10,15 +10,16 @@ where  import Control.Monad (when)-import System.Directory (findExecutable)+import System.Directory (findExecutable, makeAbsolute) import System.Environment (getEnvironment) import System.Exit (ExitCode (..)) import System.Process (CreateProcess (..), readCreateProcessWithExitCode) import System.Process qualified as Process  -- | Find a `fourmolu` executable on PATH.+-- Needs to be absolute to work around https://github.com/haskell/cabal/issues/11598 getFourmoluExe :: IO FilePath-getFourmoluExe = findExecutable "fourmolu" >>= maybe (fail "Could not find fourmolu executable") return+getFourmoluExe = findExecutable "fourmolu" >>= maybe (fail "Could not find fourmolu executable") makeAbsolute  -- | Like 'System.Process.readProcess', except without specifying stdin and -- with better failure messages.
tests/Ormolu/PrinterSpec.hs view
@@ -7,16 +7,14 @@ import Control.Exception import Control.Monad import Data.List (isSuffixOf)-import Data.Map qualified as Map import Data.Maybe (isJust)-import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO.Utf8 qualified as T.Utf8 import Data.Yaml qualified as Yaml import Ormolu import Ormolu.Config-import Ormolu.Fixity+import Ormolu.TestConfig import Path import Path.IO import System.Environment (lookupEnv)@@ -35,46 +33,20 @@       <$> [(ormoluPrinterOpts, "ormolu", "-out"), (defaultPrinterOpts, "fourmolu", "-four-out")]       <*> es --- | Fixity overrides that are to be used with the test examples.-testsuiteOverrides :: FixityOverrides-testsuiteOverrides =-  FixityOverrides-    ( Map.fromList-        [ (".=", FixityInfo InfixR 8),-          ("#", FixityInfo InfixR 5),-          (">~<", FixityInfo InfixR 3),-          ("|~|", FixityInfo InfixR 3.3),-          ("<~>", FixityInfo InfixR 3.7)-        ]-    )- -- | Check a single given example. checkExample :: (PrinterOptsTotal, String, String) -> Path Rel File -> Spec checkExample (printerOpts, label, suffix) srcPath' = it (fromRelFile srcPath' ++ " works (" ++ label ++ ")") . withNiceExceptions $ do   let srcPath = examplesDir </> srcPath'       inputPath = fromRelFile srcPath-      config =-        defaultConfig-          { cfgPrinterOpts = printerOpts,-            cfgSourceType = detectSourceType inputPath,-            cfgFixityOverrides = testsuiteOverrides,-            cfgDependencies =-              Set.fromList-                [ "base",-                  "esqueleto",-                  "hspec",-                  "lens",-                  "servant"-                ]-          }+      config = (exampleConfig inputPath) {cfgPrinterOpts = printerOpts}   expectedOutputPath <- deriveOutput srcPath suffix-  -- 1. Given input snippet of source code parse it and pretty print it.-  -- 2. Parse the result of pretty-printing again and make sure that AST-  -- is the same as AST of the original snippet. (This happens in+  -- 1. Given an input snippet of source code, parse it and pretty-print it.+  -- 2. Parse the result of pretty-printing again and make sure that its AST+  -- is the same as the AST of the original snippet. (This happens in   -- 'ormoluFile' automatically.)   formatted0 <- ormoluFile config inputPath-  -- 3. Check the output against expected output. Thus all tests should-  -- include two files: input and expected output.+  -- 3. Check the output against the expected output. Thus all tests should+  -- include two files: the input and the expected output.   whenShouldRegenerateOutput $     T.Utf8.writeFile (fromRelFile expectedOutputPath) formatted0   expected <- T.Utf8.readFile $ fromRelFile expectedOutputPath@@ -84,20 +56,20 @@   formatted1 <- ormolu config "<formatted>" formatted0   shouldMatch True formatted1 formatted0 --- | Build list of examples for testing.+-- | Build a list of examples for testing. locateExamples :: IO [Path Rel File] locateExamples =   filter isInput . snd <$> listDirRecurRel examplesDir --- | Does given path look like input path (as opposed to expected output--- path)?+-- | Does the given path look like an input path (as opposed to an expected+-- output path)? isInput :: Path Rel File -> Bool isInput path =   let s = fromRelFile path       (s', exts) = F.splitExtensions s    in exts `elem` [".hs", ".hsig"] && not ("-out" `isSuffixOf` s') --- | For given path of input file return expected name of output.+-- | For the given input file path, return the expected output name. deriveOutput :: Path Rel File -> String -> IO (Path Rel File) deriveOutput path suffix =   parseRelFile $@@ -125,7 +97,7 @@ examplesDir :: Path Rel Dir examplesDir = $(mkRelDir "data/examples") --- | Inside this wrapper 'OrmoluException' will be caught and displayed+-- | Inside this wrapper, 'OrmoluException' will be caught and displayed -- nicely using 'displayException'. withNiceExceptions ::   -- | Action that may throw the exception
+ tests/Ormolu/TestConfig.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE OverloadedStrings #-}++-- | The 'Config' that all corpora of test inputs are formatted with.+--+-- Fixity information affects the shape of operator trees, and therefore+-- both the layout and which AST element claims a comment, so every spec+-- that formats an input file has to agree on it.+module Ormolu.TestConfig+  ( exampleConfig,+  )+where++import Data.Map qualified as Map+import Data.Set qualified as Set+import Ormolu+import Ormolu.Fixity++-- | The configuration to use for a test input at the given path.+exampleConfig :: FilePath -> Config RegionIndices+exampleConfig inputPath =+  defaultConfig+    { cfgSourceType = detectSourceType inputPath,+      cfgFixityOverrides = testsuiteOverrides,+      cfgDependencies =+        Set.fromList+          [ "base",+            "esqueleto",+            "hspec",+            "lens",+            "megaparsec",+            "optics",+            "relude",+            "rio",+            "servant"+          ]+    }++-- | Fixity overrides that are to be used with the test inputs.+testsuiteOverrides :: FixityOverrides+testsuiteOverrides =+  FixityOverrides+    ( Map.fromList+        [ (".=", FixityInfo InfixR 8),+          ("#", FixityInfo InfixR 5),+          (">~<", FixityInfo InfixR 3),+          ("|~|", FixityInfo InfixR 3.3),+          ("<~>", FixityInfo InfixR 3.7)+        ]+    )