diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,490 @@
+## 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
+
+* Fixed 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).
+
+* Fixed printing of multi-line or-patterns inside as-patterns. [Issue
+  1183](https://github.com/tweag/ormolu/issues/1183).
+
+* Fixed 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).
+
+## Ormolu 0.8.0.2
+
+* Fixed a performance regression introduced in 0.8.0.0. [Issue
+  1176](https://github.com/tweag/ormolu/issues/1176).
+
+## Ormolu 0.8.0.1
+
+* Correctly format edge cases where fully collapsing string gaps changes the
+  string represented by a string literal. [Issue
+  1160](https://github.com/tweag/ormolu/issues/1160).
+
+* Fix false positives in AST diffing in fixity declarations with implicit
+  fixity, such as `infix +`. [Issue
+  1166](https://github.com/tweag/ormolu/issues/1166).
+
+* Make multiline function signatures in RequiredTypeArguments consistent with
+  types [PR 1170](https://github.com/tweag/ormolu/pull/1170).
+
+* Correctly format single-line `MultiWayIf`s. [Issue
+  1171](https://github.com/tweag/ormolu/issues/1171).
+
+## Ormolu 0.8.0.0
+
+* Format multiple files in parallel. [Issue
+  1128](https://github.com/tweag/ormolu/issues/1128).
+
+* Fractional precedences are now allowed in `.ormolu` files for more precise
+  control over formatting of complex operator chains. [Issue
+  1106](https://github.com/tweag/ormolu/issues/1106).
+
+* Correctly format type applications of `QuasiQuotes`. [Issue
+  1134](https://github.com/tweag/ormolu/issues/1134).
+
+* Correctly format multi-line parentheses in arrow `do` blocks. [Issue
+  1144](https://github.com/tweag/ormolu/issues/1144).
+
+* Switched to `ghc-lib-parser-9.12`, with the following new syntactic features:
+   * GHC proposal [#522](https://github.com/ghc-proposals/ghc-proposals/blob/c9401f037cb22d1661931b2ec621925101052997/proposals/0522-or-patterns.rst): `OrPatterns` (enabled by default)
+   * GHC proposal [#569](https://github.com/ghc-proposals/ghc-proposals/blob/c9401f037cb22d1661931b2ec621925101052997/proposals/0569-multiline-strings.rst): `MultilineStrings` (disabled by default)
+   * GHC proposal [#409](https://github.com/ghc-proposals/ghc-proposals/blob/f79438cf8dbfcd90187f7af3a380515ffe45dbdc/proposals/0409-exportable-named-default.rst): `NamedDefaults` (enabled by default)
+   * GHC proposal [#281](https://github.com/ghc-proposals/ghc-proposals/blob/c9401f037cb22d1661931b2ec621925101052997/proposals/0281-visible-forall.rst): accept more types in terms: `forall` quantifications, constraint arrows `=>`, type arrows `->` (enabled by default)
+   * Part of GHC proposal [#425](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0425-decl-invis-binders.rst): wildcard binders (enabled by default)
+
+* Correctly format non-promoted type-level tuples with `NoListTuplePuns`. [Issue
+  1146](https://github.com/tweag/ormolu/issues/1146).
+
+* Updated to `Cabal-syntax-3.14`. [Issue
+  1152](https://github.com/tweag/ormolu/issues/1152).
+
+## Ormolu 0.7.7.0
+
+* Use single-line layout for parens around single-line content. [Issue
+  1120](https://github.com/tweag/ormolu/issues/1120).
+
+* Allow function arguments to be on the same line even if the full type
+  (with constraints and foralls) are on multiple lines. [PR
+  1125](https://github.com/tweag/ormolu/pull/1125).
+
+## Ormolu 0.7.6.0
+
+* Fix Haddock comments on infix constructors. [Issue
+  758](https://github.com/tweag/ormolu/issues/758).
+
+* Don't require a trailing newline in `.ormolu` files. [Issue
+  1122](https://github.com/tweag/ormolu/issues/1122).
+
+* Remove unnecessary indentation from list comprehensions. [Issue
+  966](https://github.com/tweag/ormolu/issues/966).
+
+## Ormolu 0.7.5.0
+
+* Switched to `ghc-lib-parser-9.10`, with the following new syntactic features/behaviors:
+  * GHC proposal [#575](https://github.com/ghc-proposals/ghc-proposals/blob/10290a668608d608c3f6c6010be265cf7a02e1fc/proposals/0575-deprecated-instances.rst): deprecated instances.
+  * GHC proposal [#281](https://github.com/ghc-proposals/ghc-proposals/blob/10290a668608d608c3f6c6010be265cf7a02e1fc/proposals/0281-visible-forall.rst): visible forall in types of terms.
+    Enabled by `RequiredTypeArguments` (enabled by default).
+  * `LinearTypes`: `let` and `where` bindings can now be linear, in particular have multiplicity annotations.
+  * Using `forall` as an identifier is now a parse error.
+  * GHC proposal [#65](https://github.com/ghc-proposals/ghc-proposals/blob/10290a668608d608c3f6c6010be265cf7a02e1fc/proposals/0065-type-infix.rst): namespacing fixity declarations for type names and WARNING/DEPRECATED pragmas.
+  * `TypeAbstractions` now supports `@`-binders in lambdas and function equations.
+  * Support for the `GHC2024` language.
+
+* Updated to `Cabal-syntax-3.12`.
+
+## Ormolu 0.7.4.0
+
+* Don't error when the `JavaScriptFFI` language pragma is present. [Issue
+  1087](https://github.com/tweag/ormolu/issues/1087).
+* Improve comment placement in if-then-else blocks. [Issue
+  998](https://github.com/tweag/ormolu/issues/998).
+* Now command line options for fixity overrides and module re-exports
+  overwrite information from `.ormolu` files. [Issue
+  1030](https://github.com/tweag/ormolu/issues/1030).
+* Respect newlines in data declarations in more cases. [Issue
+  1077](https://github.com/tweag/ormolu/issues/1077) and [issue
+  947](https://github.com/tweag/ormolu/issues/947).
+* The `-d / --debug` command line option now makes Ormolu print out debug
+  information regarding operator fixity inference. [Issue
+  1060](https://github.com/tweag/ormolu/issues/1060).
+
+## Ormolu 0.7.3.0
+
+* Switched to `ghc-lib-parser-9.8`, with the following new syntactic features:
+  * `ExtendedLiterals`: `123#Int8` is a literal of type `Int8#`. (disabled by
+    default)
+  * `TypeAbstractions`: `@k`-binders in data type declarations (enabled by
+    default)
+  * GHC proposal [#134](https://github.com/ghc-proposals/ghc-proposals/blob/0b652bd70258e354dfe4a05940182007596f8bf7/proposals/0134-deprecating-exports-proposal.rst): deprecating/warning about exports
+  * GHC proposal [#541](https://github.com/ghc-proposals/ghc-proposals/blob/0b652bd70258e354dfe4a05940182007596f8bf7/proposals/0541-warning-pragmas-with-categories.rst): warning categories
+
+## Ormolu 0.7.2.0
+
+* Preserve necessary braces for final function arguments. [Issue
+  1044](https://github.com/tweag/ormolu/issues/1044).
+
+* Put `"this"` `PackageImports` at the end. [Issue
+  1048](https://github.com/tweag/ormolu/issues/1048).
+
+* Format parenthesized operators starting with a `#` correctly in the presence
+  of `UnboxedSums`. [Issue 1062](https://github.com/tweag/ormolu/issues/1062).
+
+* Fix false positives in AST diffing related to empty Haddock comments in data
+  declarations. [Issue 1065](https://github.com/tweag/ormolu/issues/1065).
+
+## Ormolu 0.7.1.0
+
+* Include `base` fixity information when formatting a Haskell file that's
+  not mentioned in an existing cabal file. [Issue
+  1032](https://github.com/tweag/ormolu/issues/1032)
+
+* Update `displayException` for `OrmoluException` to pretty print the
+  exception. [PR 1031](https://github.com/tweag/ormolu/pull/1031).
+
+* Ormolu is now aware of more common module re-exports by default.
+
+* Support explicit mention of target package name in module re-exports. Even
+  if the exported package is not specified as a direct dependency of the
+  component being formatted it will still be taken into account correctly.
+  [Issue 1037](https://github.com/tweag/ormolu/issues/1037).
+
+* Ormolu no longer fails when CPP directly follows the import section (a
+  regression introduced in 0.7.0.0). [Issue
+  1040](https://github.com/tweag/ormolu/issues/1040).
+
+## Ormolu 0.7.0.0
+
+* Inference of operator fixity information is now more precise and takes
+  into account the import section of the module being formatted. [Issue
+  892](https://github.com/tweag/ormolu/issues/892) and [issue
+  929](https://github.com/tweag/ormolu/issues/929).
+
+* Ormolu can now be made aware of module re-exports through either special
+  declarations in `.ormolu` files (see the readme for a description of the
+  syntax), or on the command line with the `--reexport`/`-r` option. [Issue
+  1017](https://github.com/tweag/ormolu/issues/1017).
+
+* Ormolu now looks for `.ormolu` files independently of `.cabal` files. This
+  means that it is now possible to have one `.ormolu` file for multiple
+  Cabal packages. [Issue 1019](https://github.com/tweag/ormolu/issues/1019).
+
+* Consistently format `do` blocks/`case`s/`MultiWayIf`s with 4 spaces if and
+  only if they occur as the applicand. [Issue
+  1002](https://github.com/tweag/ormolu/issues/1002) and [issue
+  730](https://github.com/tweag/ormolu/issues/730).
+
+* Support the (deprecated) `DatatypeContexts` extension to avoid surprises.
+  [Issue 1012](https://github.com/tweag/ormolu/issues/1012).
+
+* Don't let comments escape from empty export lists. [Issue
+  906](https://github.com/tweag/ormolu/issues/906).
+
+* Format `\cases` with multiple patterns across multiple lines correctly. [Issue
+  1025](https://github.com/tweag/ormolu/issues/1025).
+
+## Ormolu 0.6.0.1
+
+* Fix false positives in AST diffing related to `UnicodeSyntax`. [PR
+  1009](https://github.com/tweag/ormolu/pull/1009).
+
+## Ormolu 0.6.0.0
+
+* Haddocks attached to arguments of a data constructor are now formatted in
+  the pipe style (rather than the caret style), consistent with everything
+  else. As a consequence, now Ormolu's output will be deemed invalid by the
+  Haddock shipped with GHC <9.0. [Issue
+  844](https://github.com/tweag/ormolu/issues/844) and [issue
+  828](https://github.com/tweag/ormolu/issues/828).
+
+* Insert space before char literals in ticked promoted constructs when
+  necessary. [Issue 1000](https://github.com/tweag/ormolu/issues/1000).
+
+* Switched to `ghc-lib-parser-9.6`:
+  * Extended `OverloadedLabels`: `#Foo`, `#3`, `#"Hello there"`.
+
+    Also, it is now disabled by default, as it causes e.g. `a#b` to be parsed
+    differently.
+  * New extension: `TypeData`, enabled by default.
+  * Parse errors now include error codes, cf. https://errors.haskell.org.
+
+* Updated to `Cabal-syntax-3.10`.
+
+* Now whenever Ormolu fails to parse a `.cabal` file it also explains why.
+  [PR 999](https://github.com/tweag/ormolu/pull/999).
+
+## Ormolu 0.5.3.0
+
+* Stop making empty `let`s move comments. [Issue
+  917](https://github.com/tweag/ormolu/issues/917).
+
+* Now `.ormolu` fixity override files can use both LF and CRLF line endings.
+  [PR 969](https://github.com/tweag/ormolu/pull/969).
+
+* Normalize parentheses around constraints. [Issue
+  264](https://github.com/tweag/ormolu/issues/264).
+
+* The `ormolu` function now consumes `Text` instead of `String` due to an
+  internal refactoring.
+
+* Exposed a more complete public API in the `Ormolu` module. The API is
+  supposed to be stable and change according to
+  [PVP](https://pvp.haskell.org/).
+
+* Now warnings regarding Ormolu not being able to find `.cabal` files or
+  finding such files but them not mentioning the source file in question are
+  only displayed when `--debug` is used. Printing the warnings by default
+  seems to have been confusing, see e.g. [Issue
+  971](https://github.com/tweag/ormolu/issues/971) and [issue
+  924](https://github.com/tweag/ormolu/issues/924).
+
+## Ormolu 0.5.2.0
+
+* Eliminated the `fixity-th` Cabal flag because it caused issues on GHC 9.4 as
+  well as on aarch64. See [issue
+  941](https://github.com/tweag/ormolu/issues/941) and [issue
+  927](https://github.com/tweag/ormolu/issues/927).
+
+* Now operators without explicitly specified fixity default to left
+  associativity and highest precedence. [Issue
+  907](https://github.com/tweag/ormolu/issues/907).
+
+## Ormolu 0.5.1.0
+
+* Imports are now sorted by package qualifier, if one is present.
+  [Issue 905](https://github.com/tweag/ormolu/issues/905).
+
+* Extension packs like `GHC2021` and `Haskell2010` are now bumped to the top of
+  the list of language pragmas. [Issue
+  922](https://github.com/tweag/ormolu/issues/922).
+
+* Fix formatting of `SCC` pragmas in `do` blocks. [Issue
+  925](https://github.com/tweag/ormolu/issues/925).
+
+* Support type applications in patterns. [Issue
+  930](https://github.com/tweag/ormolu/issues/930).
+
+* Handle `UnicodeSyntax` variants more consistently. [Issue
+  934](https://github.com/tweag/ormolu/issues/934).
+
+* Fix an inconsistency in formatting of types in GADT declarations in
+  certain cases. [PR 932](https://github.com/tweag/ormolu/pull/932).
+
+* Switched to `ghc-lib-parser-9.4`, which brings support for the following new
+  syntactic features:
+  * `\cases` via `LambdaCase`
+  * `OPAQUE` pragmas
+  * Unboxed sum type constructors like `(# | #)`.
+
+* Updated to `Cabal-syntax-3.8`, supporting `cabal-version: 3.8`.
+
+## Ormolu 0.5.0.1
+
+* Fixed 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.
+  [Issue 893](https://github.com/tweag/ormolu/issues/893).
+
+* Fix `hs-source-dirs: .` resulting in failing to find a `.cabal` file for a
+  Haskell source file. [Issue 909](https://github.com/tweag/ormolu/issues/909).
+
+* Comments in closed type family declarations are now indented correctly.
+  [Issue 913](https://github.com/tweag/ormolu/issues/913).
+
+* Cache `.cabal` file parsing and processing when given multiple input files in
+  the same project. This results in dramatic speedups on projects which have
+  both huge `.cabal` files and a large number of individual modules. [Issue
+  897](https://github.com/tweag/ormolu/issues/897).
+
+## Ormolu 0.5.0.0
+
+* Changed the way operator fixities and precedences are inferred.
+  * Ormolu now tries to locate `.cabal` files of source files by default and
+    in addition to default extensions it also infers the list of
+    dependencies.
+  * Ormolu comes equipped with extensive knowledge of all packages on
+    Hackage and operators that those packages define. Knowing the names of
+    the dependencies it can select the right fixity and precedence info from
+    its knowledge base.
+  * You can ask Ormolu not to look for `.cabal` files by using the
+    `--no-cabal` switch.
+  * Dependencies can be selected manually by using the `-p / --package`
+    option (can be repeated many times).
+  * The default heuristic algorithm will still try to guess the right
+    fixities and precedence.
+  * Fixity overrides can be provided by the user in the familiar Haskell
+    notation (e.g. `infixr 9 .`, one declaration per line). They are loaded
+    by default from the `.ormolu` file that is expected to be in the same
+    directory as the `.cabal` file of a given source file. However, if
+    `--no-cabal` is supplied, the `.ormolu` file will not be looked for either.
+    Fixity declarations can be also provided by using the `-f / --fixity`
+    command line option, which see.
+  * This resolves the following issues: [Issue
+    826](https://github.com/tweag/ormolu/issues/826), [Issue
+    785](https://github.com/tweag/ormolu/issues/785), [Issue
+    690](https://github.com/tweag/ormolu/issues/690), [Issue
+    825](https://github.com/tweag/ormolu/issues/825).
+
+* Invalid haddock comments are formatted in a more consistent way. Leading
+  haddock triggers (`|`, `^`) in an invalid haddock comment block are now
+  escaped with a backslash `\`. [Issue
+  816](https://github.com/tweag/ormolu/issues/816).
+
+* Type synonyms and families are now formatted correctly when the equals sign
+  is preceded by a comment. [Issue 829](
+  https://github.com/tweag/ormolu/issues/829).
+
+* Bidirectional pattern synonyms are formatted nicer in certain cases.
+  [Issue 843](https://github.com/tweag/ormolu/issues/843).
+
+* Magic comments (like `{- ORMOLU_DISABLED -}`) now allow arbitrary
+  succeeding text. This fixes use cases like [Issue
+  856](https://github.com/tweag/ormolu/issues/856).
+
+* Remove discrepancies between unboxed types and terms. [Issue 856
+  ](https://github.com/tweag/ormolu/issues/856).
+  * Unboxed sum types are now formatted with a space before each `|`.
+  * Unboxed unit tuples on type and value levels are formatted as `(# #)`.
+
+* Errors caused by AST differences now print before/after diffs.
+  [Issue 877](https://github.com/tweag/ormolu/issues/877).
+
+* Improved formatting of data declarations in the case of single-constructor
+  record with a Haddock. [Issue
+  881](https://github.com/tweag/ormolu/issues/881).
+
+## Ormolu 0.4.0.0
+
+* When a guard is located on its own line, the body associated with this
+  guard is indented by one extra level, so that it can easily be
+  distinguished from the guard predicate or pattern. [Issue
+  806](https://github.com/tweag/ormolu/issues/806).
+
+* Now a space is forced after `--` in line comments. [Issue
+  808](https://github.com/tweag/ormolu/issues/808).
+
+* Allow formatting Backpack signature files (`.hsig`). The switch between
+  regular module mode and signature mode is based on the file extension by
+  default, but can be overridden with the `-t / --source-type` command line
+  option. [Issue 600](https://github.com/tweag/ormolu/issues/600).
+
+* Blank Haddock comments are now eliminated. This also fixes issues with
+  differing ASTs in some special cases. [Issue
+  726](https://github.com/tweag/ormolu/issues/726).
+
+* Rewrite rules that are never active are now formatted correctly.
+  [Issue 823](https://github.com/tweag/ormolu/issues/823).
+
+* Promoted infix data constructors are now formatted correctly. [Issue 768](
+  https://github.com/tweag/ormolu/issues/768).
+
+* Switched to `ghc-lib-parser-9.2`.
+  [Issue 794](https://github.com/tweag/ormolu/issues/794).
+   * Support for the new syntax-related language extensions:
+     `OverloadedRecordDot` and `OverloadedRecordUpdate`
+     (disabled by default).
+     [Issue 709](https://github.com/tweag/ormolu/issues/709).
+   * Removed support for `record-dot-preprocessor`. For the getter syntax,
+     consider using `OverloadedRecordDot` instead. [Issue
+     659](https://github.com/tweag/ormolu/issues/659). [Issue
+     705](https://github.com/tweag/ormolu/issues/705).
+   * Support for the `GHC2021` language.
+
+## Ormolu 0.3.1.0
+
+* Allow check mode when working with stdin input. [Issue 634](
+  https://github.com/tweag/ormolu/issues/634).
+
+* Now guards are printed on a new line if at least one guard is multiline or
+  if all guards together occupy more than one line. The body of each guard
+  is also indented one level deeper in that case. [Issue
+  712](https://github.com/tweag/ormolu/issues/712).
+
+* Invalid Haddock comments are no longer silently deleted, but rather converted
+  into regular comments. [Issue 474](https://github.com/tweag/ormolu/issues/474).
+
+## Ormolu 0.3.0.1
+
+* Improvements to `.cabal` file handling:
+   * When looking for a `.cabal` file, directories were previously
+     erroneously also considered. [Issue 781](
+     https://github.com/tweag/ormolu/issues/781).
+   * We now print a note if Ormolu was told to consider
+     `.cabal` files, but no suitable one could be found.
+   * Handle an empty `hs-source-dirs` correctly.
+   * Also consider modules which are only conditionally listed
+     in the `.cabal` file.
+
+* The special handling of CPP is now only applied if CPP is
+  actually enabled. [Issue 782](https://github.com/tweag/ormolu/issues/782).
+
+* The left hand side of the `:` operator now uses braces if
+  necessary. [Issue 780](https://github.com/tweag/ormolu/issues/780).
+
+## Ormolu 0.3.0.0
+
+* Data declarations with multiline kind signatures are now formatted
+  correctly. [Issue 749](https://github.com/tweag/ormolu/issues/749).
+
+* Infix arrow command formations are formatted like usual operators.
+  This fixes [Issue 748](https://github.com/tweag/ormolu/issues/748).
+
+* `do` arrow commands are formatted more flexibly. Fixes [Issue
+  753](https://github.com/tweag/ormolu/issues/753).
+
+* Source code is always read and written using UTF8 and ignoring the native
+  line ending conventions. [Issue
+  717](https://github.com/tweag/ormolu/issues/717).
+
+* Opt-in support to respect default-extensions and default-language
+  from .cabal files. [Issue 517](https://github.com/tweag/ormolu/issues/517).
+
+* Empty case expressions are now rendered with braces. [Issue
+  765](https://github.com/tweag/ormolu/issues/765).
+
+* Omit braces on repeated application of `do` blocks. [Issue
+  735](https://github.com/tweag/ormolu/issues/735).
+
+* Improved handling of disabled regions. [PR 773](
+  https://github.com/tweag/ormolu/pull/773).
+   * Disabled regions are now exactly preserved, in particular
+     empty lines and trailing spaces. [Issue
+     673](https://github.com/tweag/ormolu/issues/673).
+   * Strings like `-}` can now be present in disabled regions.
+     [Issue 708](https://github.com/tweag/ormolu/issues/708).
+
+  This means that using CPP or magic comments in certain ways which
+  were only supported as a side effect previously (like in
+  [Issue 601](https://github.com/tweag/ormolu/issues/601))
+  will now result in formatting failures. Also see [Issue 774](
+  https://github.com/tweag/ormolu/issues/774).
+
 ## Ormolu 0.2.0.0
 
 * Now standalone kind signatures are grouped with type synonyms. [Issue
@@ -54,7 +541,7 @@
   Previously, if an extension was disabled via the CLI, it could not be
   re-enabled per file.
 
-* `LexicalNegation` is no longer enabled by default. Also, spaces after
+* `NegativeLiterals` is no longer enabled by default. Also, spaces after
   negation via `-` are removed where possible. [Issue
   694](https://github.com/tweag/ormolu/issues/694).
 
@@ -64,7 +551,8 @@
 * Added support for left-to-right arrow application. [Issue
   737](https://github.com/tweag/ormolu/issues/737).
 
-* Now `--mode check` fails on missing trailing blank lines.
+* 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
   739](https://github.com/tweag/ormolu/issues/739).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,11 +13,7 @@
 Please make sure that all non-trivial changes are described in commit
 messages and PR descriptions.
 
-## What to hack on?
-
-* [Fixing bugs][bugs]. This is the main focus right now.
-
-### Testing
+## Testing
 
 Testing has been taken good care of and now it amounts to just adding
 examples under `data/examples`. Each example is a pair of files:
@@ -39,26 +35,18 @@
 Please note that we try to keep individual files at most 25 lines long
 because otherwise it's hard to figure out want went wrong when a test fails.
 
-## CI
-
-We use Circle CI. Some outside contributors may have problems, as in, CI
-won't run for PRs opened from forks with “unauthorized” errors. In that case
-the best we can do is to add you as a contributor or to restart your build
-manually.
-
-If you have been added as a contributor but the builds still do not start,
-try clicking
+To regenerate outputs that have changed, you can set the
+`ORMOLU_REGENERATE_EXAMPLES` environment variable before running tests.
 
-```
-User settings -> Account integrations -> Refresh permissions
-```
+## Formatting
 
-in Circle CI app.
+ - Use `nix run .#format` script to format Ormolu with the current version of
+   Ormolu.
 
-## Formatting
+ - Additional formatters are configured via a pre-commit hook which is
+   automatically installed when entering the Nix shell. You can also run it via
+   `pre-commit run`/`pre-commit run -a`.
 
-Use `format.sh` script to format Ormolu with current version of Ormolu. If
-Ormolu is not formatted like this, the CI will fail.
+If Ormolu is not formatted like this, the CI will fail.
 
 [issues]: https://github.com/tweag/ormolu/issues
-[bugs]: https://github.com/tweag/ormolu/issues?q=is%3Aissue+is%3Aopen+label%3Abug
diff --git a/DESIGN.md b/DESIGN.md
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -65,7 +65,7 @@
 of which fit in linear space. So care is necessary to keep memory
 bounded.
 
-The compexities of the `BriDoc` structure, together with the lack of
+The complexities of the `BriDoc` structure, together with the lack of
 documentation, make Brittany at least challenging to maintain.
 
 ### Hindent
@@ -107,13 +107,13 @@
 from the same upstream problems. I haven't studied the transformations it
 performs, but it looks like it transforms the parsed source code partially
 by manipulating AST and partially by manipulating raw text (e.g. to drop
-trailing whitspace from each line). CPP Macros are just filtered out
+trailing whitespace from each line). CPP Macros are just filtered out
 silently as a preprocessing step before feeding the code to
 `haskell-src-exts`.
 
 Stylish Haskell is not so invasive as the other formatters and most reported
 bugs are about parsing issues and CPP. As I understand it, people mostly use
-it to screw their import lists.
+it to sort their import lists.
 
 ### Haskell formatter
 
@@ -225,7 +225,7 @@
 directives, constraining how directives can be inserted in Haskell
 code to avoid changing the meaning by reformatting. But
 this would introduce additional complexity, and the problem would
-need to be solved repeteadly for every tool out there which wants
+need to be solved repeatedly for every tool out there which wants
 to parse Haskell modules. If CPP is replaced with some language
 extension or mechanism to do conditional compilation, all tools
 will benefit from it.
@@ -366,7 +366,7 @@
 * An option to specify location of config file.
 * Options to specify parameters that come from config files on command line
   instead (currently this is just dynamic options enabled by default, such
-  as langauge extensions).
+  as language extensions).
 
 ### Why not contribute to/fork HIndent or Brittany?
 
@@ -386,7 +386,7 @@
 Forking or contributing to Hindent is not an option because if we replace
 `haskell-src-exts` with `ghc` (or `ghc-exact-print`) then we'll have to work
 with a different AST type and all the code in Hindent will become
-incompatible and there won't be much code to be re-used in that case. It is
+incompatible and there won't be much code to be reused in that case. It is
 also possible that we'll find a nicer way to write pretty-printer.
 
 ## Examples
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,17 +4,25 @@
 [![Hackage](https://img.shields.io/hackage/v/ormolu.svg?style=flat)](https://hackage.haskell.org/package/ormolu)
 [![Stackage Nightly](http://stackage.org/package/ormolu/badge/nightly)](http://stackage.org/nightly/package/ormolu)
 [![Stackage LTS](http://stackage.org/package/ormolu/badge/lts)](http://stackage.org/lts/package/ormolu)
-[![Build status](https://badge.buildkite.com/8e3b0951f3652b77e1c422b361904136a539b0522029156354.svg?branch=master)](https://buildkite.com/tweag-1/ormolu)
+[![CI](https://github.com/tweag/ormolu/actions/workflows/ci.yml/badge.svg)](https://github.com/tweag/ormolu/actions/workflows/ci.yml)
 
-* [Building and installation](#building-and-installation)
-    * [Arch Linux](#arch-linux)
+* [Installation](#installation)
+* [Building from source](#building-from-source)
 * [Usage](#usage)
+    * [Ormolu Live](#ormolu-live)
     * [Editor integration](#editor-integration)
+    * [Haskell Language Server](#haskell-language-server)
     * [GitHub actions](#github-actions)
+    * [Language extensions, dependencies, and fixities](#language-extensions-dependencies-and-fixities)
     * [Magic comments](#magic-comments)
+    * [Regions](#regions)
     * [Exit codes](#exit-codes)
+    * [Using as a library](#using-as-a-library)
+* [Troubleshooting](#troubleshooting)
+    * [Operators are being formatted weirdly!](#operators-are-being-formatted-weirdly)
 * [Limitations](#limitations)
 * [Running on Hackage](#running-on-hackage)
+* [Forks and modifications](#forks-and-modifications)
 * [Contributing](#contributing)
 * [License](#license)
 
@@ -38,64 +46,56 @@
 * Be well-tested and robust so that the formatter can be used in large
   projects.
 
-## Building and installation
+Try it out in your browser at <https://ormolu-live.tweag.io>!
+See [Ormolu Live](#ormolu-live) for more info.
 
-The easiest way to build the project is with Nix:
+## Installation
 
+The [release page][releases] has binaries for Linux, macOS and Windows.
+
+You can also install using `cabal` or `stack`:
+
 ```console
-$ nix-build -A ormolu
+$ cabal install ormolu
+$ stack install ormolu
 ```
 
-Or with `cabal-install` from the Nix shell:
+Ormolu is also included in several package repositories. E.g., on Arch Linux,
+one can use [the package on AUR][aur]:
 
 ```console
-$ nix-shell --run "cabal new-build"
+$ yay -S ormolu
 ```
 
-Alternatively, `stack` could be used with a `stack.yaml` file as follows.
+## Building from source
 
+The easiest way to build the project is with Nix:
+
 ```console
-$ cat stack.yaml
-resolver: lts-16.0
-packages:
-- '.'
+$ nix build
+```
 
+Make sure to accept the offered Nix caches (in particular the IOG cache),
+otherwise building may take a very long time.
+
+Alternatively, `stack` could be used as follows:
+
+```console
 $ stack build # to build
 $ stack install # to install
 ```
 
-To use Ormolu directly from GitHub with Nix, this snippet may come in handy:
+To use Ormolu directly from GitHub with Nix flakes, this snippet may come in handy:
 
 ```nix
-# This overlay adds Ormolu straight from GitHub.
-self: super:
-
-let source = super.fetchFromGitHub {
-      owner = "tweag";
-      repo = "ormolu";
-      rev = "de279d80122b287374d4ed87c7b630db1f157642"; # update as necessary
-      sha256 = "0qrxfk62ww6b60ha9sqcgl4nb2n5fhf66a65wszjngwkybwlzmrv"; # same
-    };
-    ormolu = import source { pkgs = self; };
-in {
-  haskell = super.haskell // {
-    packages = super.haskell.packages // {
-      "${ormolu.ormoluCompiler}" = super.haskell.packages.${ormolu.ormoluCompiler}.override {
-        overrides = ormolu.ormoluOverlay;
-      };
-    };
+{
+  inputs.ormolu.url = "github:tweag/ormolu";
+  outputs = { ormolu, ... }: {
+    # use ormolu.packages.${system}.default here
   };
 }
 ```
 
-### Arch Linux
-
-To install Ormolu on Arch Linux, one can use [the package on AUR][aur]:
-
-```console
-yay -S ormolu
-```
-
 ## Usage
 
 The following will print the formatted output to the standard output.
@@ -129,6 +129,20 @@
 $ ormolu --mode check $(find . -name '*.hs')
 ```
 
+#### :zap: Beware git's `core.autocrlf` on Windows :zap:
+Ormolu's output always uses LF line endings. In particular,
+`ormolu --mode check` will fail if its input is correctly formatted
+*except* that it has CRLF line endings. This situation can happen on Windows
+when checking out a git repository without having set [`core.autocrlf`](
+https://www.git-scm.com/docs/git-config#Documentation/git-config.txt-coreautocrlf)
+to `false`.
+
+### Ormolu Live
+
+On every new commit to `master`, [Ormolu Live](./ormolu-live) is deployed to
+https://ormolu-live.tweag.io. Older versions are available at
+https://COMMITHASH--ormolu-live.netlify.app.
+
 ### Editor integration
 
 We know of the following editor integrations:
@@ -137,11 +151,81 @@
 * [VS Code][vs-code-plugin]
 * Vim: [neoformat][neoformat], [vim-ormolu][vim-ormolu]
 
+### Haskell Language Server
+
+[Haskell Language Server](https://haskell-language-server.readthedocs.io)
+has built-in support for using Ormolu as a formatter.
+
 ### GitHub actions
 
-[`ormolu-action`][ormolu-action] is the recommended way to ensure that a
-project is formatted with Ormolu.
+[`run-ormolu`][run-ormolu] is the recommended way to ensure that a project
+is formatted with Ormolu.
 
+### Language extensions, dependencies, and fixities
+
+Ormolu 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 an `.ormolu`
+file which should be located at a higher level in the file system hierarchy
+than the source file that is being formatted. When the input comes from
+stdin, one can pass `--stdin-input-file` which will give Ormolu the location
+that should be used as the starting point for searching for `.cabal` and
+`.ormolu` files.
+
+Here is an example of `.ormolu` file:
+
+```haskell
+infixr 9  .
+infixr 5  ++
+infixl 4  <$
+infixl 1  >>, >>=
+infixr 1  =<<
+infixr 0  $, $!
+infixl 4 <*>, <*, *>, <**>
+
+infixr 3 >~<
+infixr 3.3 |~|
+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.
+
+As of Ormolu 0.7.0.0, `.ormolu` files can also contain instructions about
+module re-exports that Ormolu should be aware of. This might be desirable
+because at the moment Ormolu 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 Ormolu. (You are welcome to open PRs to make Ormolu aware of more
+re-exports by default.) However, when the fixity of an operator is not
+inferred correctly, making Ormolu aware of a re-export may come in handy.
+Here is an example:
+
+```haskell
+module Control.Lens exports Control.Lens.At
+module Control.Lens exports "lens" Control.Lens.Lens
+```
+
+Module re-export declarations can be mixed freely with fixity overrides, as
+long as each declaration is on its own line. As of Ormolu 0.7.1.0 explicit
+package names are allowed in re-export declarations (see the example above).
+
+Finally, all of the above-mentioned parameters can be controlled from the
+command line:
+
+* Language extensions can be specified with the `-o` or `--ghc-opt` flag.
+* Dependencies can be specified with the `-p` or `--package` flag.
+* Fixities can be specified with the `-f` or `--fixity` flag.
+* Re-exports can be specified with the `-r` or `--reexport` flag.
+
+Searching for `.cabal` and `.ormolu` files can be disabled by passing
+`--no-cabal` and `--no-dot-ormolu` respectively.
+
 ### Magic comments
 
 Ormolu understands two magic comments:
@@ -159,10 +243,19 @@
 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
 `{- ORMOLU_DISABLE -}` at the very top. Note that for Ormolu to work the
-source code must still be parseable even when the disabled regions are
-omitted. Because of that the magic comments cannot be placed arbitrarily,
-but rather must enclose independent top-level definitions.
+fragments where Ormolu 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.
 
+### Regions
+
+One can ask Ormolu 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.
+
+Note that the selected region needs to be parseable Haskell code on its own.
+
 ### Exit codes
 
 Exit code | Meaning
@@ -175,10 +268,46 @@
 5         | AST of original and formatted code differs
 6         | Formatting is not idempotent
 7         | Unrecognized GHC options
+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
 100       | In checking mode: unformatted files
-101       | Inplace and check modes do not work with stdin
+101       | Inplace mode does not work with stdin
 102       | Other issue (with multiple input files)
 
+### Using as a library
+
+The `ormolu` 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.5.3.0. Rely on other modules at your own risk.
+
+## Troubleshooting
+
+### Operators are being formatted weirdly!
+
+This can happen when Ormolu doesn't know or can't determine the fixity of an
+operator.
+
+* If this is a custom operator, see the instructions in the [Language
+  extensions, dependencies, and
+  fixities](#language-extensions-dependencies-and-fixities) section to
+  specify the correct fixities in a `.ormolu` 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
+  same as the third-party one.
+
+  Some reasons this might be the case:
+
+    * You might have a custom Prelude that re-exports things from Prelude
+    * You might have `-XNoImplicitPrelude` turned on
+
+  If any of these are true, make sure to specify the reexports correctly in
+  a `.ormolu` file.
+
+You can see how Ormolu decides the fixity of operators if you use `--debug`.
+
 ## Limitations
 
 * CPP support is experimental. CPP is virtually impossible to handle
@@ -186,8 +315,6 @@
   works only in simple cases when CPP conditionals surround top-level
   declarations. See the [CPP][design-cpp] section in the design notes for a
   discussion of the dangers.
-* Input modules should be parsable by Haddock, which is a bit stricter
-  criterion than just being valid Haskell modules.
 
 ## Running on Hackage
 
@@ -195,7 +322,7 @@
 execute (from the root of the cloned repo):
 
 ```console
-$ nix-build -A hackage.<package>
+$ nix build .#hackage.<package>
 ```
 
 Then inspect `result/log.txt` for possible problems. The derivation will
@@ -203,6 +330,13 @@
 `.hs-original` extension (those are with CPP dropped, exactly what is fed
 into Ormolu).
 
+## Forks and modifications
+
+We know of the following actively maintained forks:
+
+* [Fourmolu][fourmolu], which uses 4-space indentation and allows arbitrary
+  configuration.
+
 ## Contributing
 
 See [CONTRIBUTING.md][contributing].
@@ -214,12 +348,14 @@
 Copyright © 2018–present Tweag I/O
 
 [aur]: https://aur.archlinux.org/packages/ormolu
-[contributing]: https://github.com/tweag/ormolu/blob/master/CONTRIBUTING.md
 [design-cpp]: https://github.com/tweag/ormolu/blob/master/DESIGN.md#cpp
 [emacs-package]: https://github.com/vyorkin/ormolu.el
 [haskell-src-exts]: https://hackage.haskell.org/package/haskell-src-exts
-[license]: https://github.com/tweag/ormolu/blob/master/LICENSE.md
 [neoformat]: https://github.com/sbdchd/neoformat
-[ormolu-action]: https://github.com/marketplace/actions/ormolu-action
+[releases]: https://github.com/tweag/ormolu/releases
+[run-ormolu]: https://github.com/haskell-actions/run-ormolu
 [vim-ormolu]: https://github.com/sdiehl/vim-ormolu
 [vs-code-plugin]: https://marketplace.visualstudio.com/items?itemName=sjurmillidahl.ormolu-vscode
+[fourmolu]: https://github.com/fourmolu/fourmolu
+[contributing]: https://github.com/tweag/ormolu/blob/master/CONTRIBUTING.md
+[license]: https://github.com/tweag/ormolu/blob/master/LICENSE.md
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-module Main (main) where
-
-import Distribution.Simple
-
-main :: IO ()
-main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,34 +1,55 @@
 {-# LANGUAGE CPP #-}
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TypeApplications #-}
 
 module Main (main) where
 
+import Control.Concurrent (MVar, newMVar, withMVar)
+import Control.Exception (throwIO)
 import Control.Monad
 import Data.Bool (bool)
 import Data.List (intercalate, sort)
-import Data.Maybe (mapMaybe)
-import qualified Data.Text.IO as TIO
+import Data.List.NonEmpty (NonEmpty)
+import Data.Map.Strict qualified as Map
+import Data.Maybe (fromMaybe, mapMaybe, maybeToList)
+import Data.Set qualified as Set
+import Data.Text.IO.Utf8 qualified as T.Utf8
 import Data.Version (showVersion)
-import Development.GitRev
+import Distribution.ModuleName (ModuleName)
+import Distribution.Types.PackageName (PackageName)
+import Language.Haskell.TH.Env (envQ)
 import Options.Applicative
 import Ormolu
 import Ormolu.Diff.Text (diffText, printTextDiff)
+import Ormolu.Fixity
 import Ormolu.Parser (manualExts)
 import Ormolu.Terminal
 import Ormolu.Utils (showOutputable)
+import Ormolu.Utils.Fixity
 import Paths_ormolu (version)
+import System.Directory
 import System.Exit (ExitCode (..), exitWith)
-import qualified System.FilePath as FP
+import System.FilePath qualified as FP
 import System.IO (hPutStrLn, stderr)
+import UnliftIO.Async (pooledMapConcurrently)
 
 -- | Entry point of the program.
 main :: IO ()
 main = do
   Opts {..} <- execParser optsParserInfo
-  let formatOne' = formatOne optMode optConfig
+  -- We use this to guard writes to stdout in order to avoid
+  -- garbled output from concurrent formatting processes.
+  outputLock <- newMVar ()
+  let formatOne' =
+        formatOne
+          optConfigFileOpts
+          optMode
+          optSourceType
+          optConfig
+          outputLock
   exitCode <- case optInputFiles of
     [] -> formatOne' Nothing
     ["-"] -> formatOne' Nothing
@@ -38,7 +59,8 @@
             ExitSuccess -> Nothing
             ExitFailure n -> Just n
       errorCodes <-
-        mapMaybe selectFailure <$> mapM (formatOne' . Just) (sort xs)
+        mapMaybe selectFailure
+          <$> pooledMapConcurrently (formatOne' . Just) (sort xs)
       return $
         if null errorCodes
           then ExitSuccess
@@ -51,59 +73,149 @@
 
 -- | Format a single input.
 formatOne ::
+  -- | How to use .cabal files
+  ConfigFileOpts ->
   -- | Mode of operation
   Mode ->
+  -- | The 'SourceType' requested by the user
+  Maybe SourceType ->
   -- | Configuration
   Config RegionIndices ->
+  -- | Lock for writing to output handles
+  MVar () ->
   -- | File to format or stdin as 'Nothing'
   Maybe FilePath ->
   IO ExitCode
-formatOne mode config mpath = withPrettyOrmoluExceptions (cfgColorMode config) $
-  case FP.normalise <$> mpath of
-    Nothing -> do
-      r <- ormoluStdin config
-      case mode of
-        Stdout -> do
-          TIO.putStr r
-          return ExitSuccess
-        _ -> do
-          hPutStrLn
-            stderr
-            "This feature is not supported when input comes from stdin."
-          -- 101 is different from all the other exit codes we already use.
-          return (ExitFailure 101)
-    Just inputFile -> do
-      originalInput <- TIO.readFile inputFile
-      formattedInput <- ormoluFile config inputFile
-      case mode of
-        Stdout -> do
-          TIO.putStr formattedInput
-          return ExitSuccess
-        InPlace -> do
-          -- Only write when the contents have changed, in order to avoid
-          -- updating the modified timestamp if the file was already correctly
-          -- formatted.
-          when (formattedInput /= originalInput) $
-            TIO.writeFile inputFile formattedInput
-          return ExitSuccess
-        Check ->
-          case diffText originalInput formattedInput inputFile of
-            Nothing -> return ExitSuccess
-            Just diff -> do
-              runTerm (printTextDiff diff) (cfgColorMode config) stderr
-              -- 100 is different to all the other exit code that are emitted
-              -- either from an 'OrmoluException' or from 'error' and
-              -- 'notImplemented'.
-              return (ExitFailure 100)
+formatOne ConfigFileOpts {..} mode reqSourceType rawConfig outputLock mpath =
+  withPrettyOrmoluExceptions (cfgColorMode rawConfig) $ do
+    let getCabalInfoForSourceFile' sourceFile = do
+          cabalSearchResult <- getCabalInfoForSourceFile sourceFile
+          let debugEnabled = cfgDebug rawConfig
+          case cabalSearchResult of
+            CabalNotFound -> do
+              when debugEnabled $
+                withMVar outputLock $ \_ ->
+                  hPutStrLn stderr $
+                    "Could not find a .cabal file for " <> sourceFile
+              return Nothing
+            CabalDidNotMention cabalInfo -> do
+              when debugEnabled $ do
+                relativeCabalFile <-
+                  makeRelativeToCurrentDirectory (ciCabalFilePath cabalInfo)
+                withMVar outputLock $ \_ ->
+                  hPutStrLn stderr $
+                    "Found .cabal file "
+                      <> relativeCabalFile
+                      <> ", but it did not mention "
+                      <> sourceFile
+              return (Just cabalInfo)
+            CabalFound cabalInfo -> return (Just cabalInfo)
+        getDotOrmoluForSourceFile' sourceFile = do
+          if optDoNotUseDotOrmolu
+            then return Nothing
+            else Just <$> getDotOrmoluForSourceFile sourceFile
+    case FP.normalise <$> mpath of
+      -- input source = STDIN
+      Nothing -> do
+        mcabalInfo <- case (optStdinInputFile, optDoNotUseCabal) of
+          (_, True) -> return Nothing
+          (Nothing, False) -> throwIO OrmoluMissingStdinInputFile
+          (Just inputFile, False) -> getCabalInfoForSourceFile' inputFile
+        mdotOrmolu <- case optStdinInputFile of
+          Nothing -> return Nothing
+          Just inputFile -> getDotOrmoluForSourceFile' inputFile
+        config <- patchConfig Nothing mcabalInfo mdotOrmolu
+        case mode of
+          Stdout -> do
+            output <- ormoluStdin config
+            withMVar outputLock $ \_ ->
+              T.Utf8.putStr output
+            return ExitSuccess
+          InPlace -> do
+            hPutStrLn
+              stderr
+              "In place editing is not supported when input comes from stdin."
+            -- 101 is different from all the other exit codes we already use.
+            return (ExitFailure 101)
+          Check -> do
+            -- ormoluStdin is not used because we need the originalInput
+            originalInput <- T.Utf8.getContents
+            let stdinRepr = "<stdin>"
+            formattedInput <-
+              ormolu config stdinRepr originalInput
+            handleDiff originalInput formattedInput stdinRepr
+      -- input source = a file
+      Just inputFile -> do
+        mcabalInfo <-
+          if optDoNotUseCabal
+            then return Nothing
+            else getCabalInfoForSourceFile' inputFile
+        mdotOrmolu <- getDotOrmoluForSourceFile' inputFile
+        config <-
+          patchConfig
+            (Just (detectSourceType inputFile))
+            mcabalInfo
+            mdotOrmolu
+        case mode of
+          Stdout -> do
+            output <- ormoluFile config inputFile
+            withMVar outputLock $ \_ ->
+              T.Utf8.putStr output
+            return ExitSuccess
+          InPlace -> do
+            -- ormoluFile is not used because we need originalInput
+            originalInput <- T.Utf8.readFile inputFile
+            formattedInput <-
+              ormolu config inputFile originalInput
+            when (formattedInput /= originalInput) $
+              T.Utf8.writeFile inputFile formattedInput
+            return ExitSuccess
+          Check -> do
+            -- ormoluFile is not used because we need originalInput
+            originalInput <- T.Utf8.readFile inputFile
+            formattedInput <-
+              ormolu config inputFile originalInput
+            handleDiff originalInput formattedInput inputFile
+  where
+    patchConfig mdetectedSourceType mcabalInfo mdotOrmolu = do
+      let sourceType =
+            fromMaybe
+              ModuleSource
+              (reqSourceType <|> mdetectedSourceType)
+      return $
+        refineConfig
+          sourceType
+          mcabalInfo
+          (Just (cfgFixityOverrides rawConfig))
+          (Just (cfgModuleReexports rawConfig))
+          ( rawConfig
+              { cfgFixityOverrides = maybe defaultFixityOverrides fst mdotOrmolu,
+                cfgModuleReexports = maybe defaultModuleReexports snd mdotOrmolu
+              }
+          )
+    handleDiff originalInput formattedInput fileRepr =
+      case diffText originalInput formattedInput fileRepr of
+        Nothing -> return ExitSuccess
+        Just diff -> do
+          runTerm (printTextDiff diff) (cfgColorMode rawConfig) stderr
+          -- 100 is different to all the other exit code that are emitted
+          -- either from an 'OrmoluException' or from 'error' and
+          -- 'notImplemented'.
+          return (ExitFailure 100)
 
 ----------------------------------------------------------------------------
 -- Command line options parsing
 
+-- | All command line options.
 data Opts = Opts
   { -- | Mode of operation
     optMode :: !Mode,
     -- | Ormolu 'Config'
     optConfig :: !(Config RegionIndices),
+    -- | Options related to info extracted from files
+    optConfigFileOpts :: ConfigFileOpts,
+    -- | Source type option, where 'Nothing' means autodetection
+    optSourceType :: !(Maybe SourceType),
     -- | Haskell source files to format or stdin (when the list is empty)
     optInputFiles :: ![FilePath]
   }
@@ -119,6 +231,18 @@
     Check
   deriving (Eq, Show)
 
+-- | Options related to configuration stored in the file system.
+data ConfigFileOpts = ConfigFileOpts
+  { -- | DO NOT extract default-extensions and dependencies from .cabal files
+    optDoNotUseCabal :: Bool,
+    -- | DO NOT look for @.ormolu@ files
+    optDoNotUseDotOrmolu :: Bool,
+    -- | Optional path to a file which will be used to find a .cabal file
+    -- when using input from stdin
+    optStdinInputFile :: Maybe FilePath
+  }
+  deriving (Show)
+
 optsParserInfo :: ParserInfo Opts
 optsParserInfo =
   info (helper <*> ver <*> exts <*> optsParser) . mconcat $
@@ -134,12 +258,9 @@
     verStr =
       intercalate
         "\n"
-        [ unwords
-            [ "ormolu",
-              showVersion version,
-              $gitBranch,
-              $gitHash
-            ],
+        [ unwords $
+            ["ormolu", showVersion version]
+              <> maybeToList $$(envQ @String "ORMOLU_REV"),
           "using ghc-lib-parser " ++ VERSION_ghc_lib_parser
         ]
     exts :: Parser (a -> a)
@@ -166,11 +287,29 @@
               ]
         )
     <*> configParser
+    <*> configFileOptsParser
+    <*> sourceTypeParser
     <*> (many . strArgument . mconcat)
       [ metavar "FILE",
         help "Haskell source files to format or stdin (the default)"
       ]
 
+configFileOptsParser :: Parser ConfigFileOpts
+configFileOptsParser =
+  ConfigFileOpts
+    <$> (switch . mconcat)
+      [ long "no-cabal",
+        help "Do not extract default-extensions and dependencies from .cabal files"
+      ]
+    <*> (switch . mconcat)
+      [ long "no-dot-ormolu",
+        help "Do not look for .ormolu files"
+      ]
+    <*> (optional . strOption . mconcat)
+      [ long "stdin-input-file",
+        help "Path which will be used to find the .cabal file when using input from stdin"
+      ]
+
 configParser :: Parser (Config RegionIndices)
 configParser =
   Config
@@ -180,6 +319,32 @@
         metavar "OPT",
         help "GHC options to enable (e.g. language extensions)"
       ]
+    <*> ( fmap (FixityOverrides . Map.fromList . mconcat)
+            . many
+            . option parseFixityDeclaration
+            . mconcat
+        )
+      [ long "fixity",
+        short 'f',
+        metavar "FIXITY",
+        help "Fixity declaration to use (an override)"
+      ]
+    <*> ( fmap (ModuleReexports . Map.fromListWith (<>) . mconcat . pure)
+            . many
+            . option parseModuleReexportDeclaration
+            . mconcat
+        )
+      [ long "reexport",
+        short 'r',
+        metavar "REEXPORT",
+        help "Module re-export that Ormolu should know about"
+      ]
+    <*> (fmap Set.fromList . many . strOption . mconcat)
+      [ long "package",
+        short 'p',
+        metavar "PACKAGE",
+        help "Explicitly specified dependency (for operator fixity/precedence only)"
+      ]
     <*> (switch . mconcat)
       [ long "unsafe",
         short 'u',
@@ -195,6 +360,10 @@
         short 'c',
         help "Fail if formatting is not idempotent"
       ]
+    -- We cannot parse the source type here, because we might need to do
+    -- autodetection based on the input file extension (not available here)
+    -- before storing the resolved value in the config struct.
+    <*> pure ModuleSource
     <*> (option parseColorMode . mconcat)
       [ long "color",
         metavar "WHEN",
@@ -214,6 +383,16 @@
               ]
         )
 
+sourceTypeParser :: Parser (Maybe SourceType)
+sourceTypeParser =
+  (option parseSourceType . mconcat)
+    [ long "source-type",
+      short 't',
+      metavar "TYPE",
+      value Nothing,
+      help "Set the type of source; TYPE can be 'module', 'sig', or 'auto' (the default)"
+    ]
+
 ----------------------------------------------------------------------------
 -- Helpers
 
@@ -225,9 +404,28 @@
   "check" -> Right Check
   s -> Left $ "unknown mode: " ++ s
 
+-- | Parse a fixity declaration.
+parseFixityDeclaration :: ReadM [(OpName, FixityInfo)]
+parseFixityDeclaration = eitherReader parseFixityDeclarationStr
+
+-- | Parse a module reexport declaration.
+parseModuleReexportDeclaration ::
+  ReadM (ModuleName, NonEmpty (Maybe PackageName, ModuleName))
+parseModuleReexportDeclaration = eitherReader parseModuleReexportDeclarationStr
+
+-- | Parse 'ColorMode'.
 parseColorMode :: ReadM ColorMode
 parseColorMode = eitherReader $ \case
   "never" -> Right Never
   "always" -> Right Always
   "auto" -> Right Auto
   s -> Left $ "unknown color mode: " ++ s
+
+-- | Parse the 'SourceType'. 'Nothing' means that autodetection based on
+-- file extension is requested.
+parseSourceType :: ReadM (Maybe SourceType)
+parseSourceType = eitherReader $ \case
+  "module" -> Right (Just ModuleSource)
+  "sig" -> Right (Just SignatureSource)
+  "auto" -> Right Nothing
+  s -> Left $ "unknown source type: " ++ s
diff --git a/data/cabal-tests/Bar.hs b/data/cabal-tests/Bar.hs
new file mode 100644
--- /dev/null
+++ b/data/cabal-tests/Bar.hs
@@ -0,0 +1,3 @@
+module Foo where
+
+import Data.List qualified as List
diff --git a/data/cabal-tests/Foo.hs b/data/cabal-tests/Foo.hs
new file mode 100644
--- /dev/null
+++ b/data/cabal-tests/Foo.hs
@@ -0,0 +1,3 @@
+module Foo where
+
+import Data.List qualified as List
diff --git a/data/cabal-tests/test.cabal b/data/cabal-tests/test.cabal
new file mode 100644
--- /dev/null
+++ b/data/cabal-tests/test.cabal
@@ -0,0 +1,13 @@
+cabal-version: 2.4
+name: test
+version: 0
+
+library
+  exposed-modules: Foo
+  hs-source-dirs: .
+  default-extensions: ImportQualifiedPost
+
+executable app
+  main-is: Main
+  other-modules: Bar
+  default-extensions: ImportQualifiedPost
diff --git a/data/diff-tests/inputs/applicative-after.hs b/data/diff-tests/inputs/applicative-after.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/applicative-after.hs
@@ -0,0 +1,8 @@
+testPermParser :: Permutation Parser String
+testPermParser =
+  f
+    <$> toPermutationWithDefault 'x' (char 'a')
+    <*> toPermutationWithDefault 'y' (char 'b')
+    <*> toPermutationWithDefault 'z' (char 'c')
+  where
+    f a b c = [a, b, c]
diff --git a/data/diff-tests/inputs/applicative-before.hs b/data/diff-tests/inputs/applicative-before.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/applicative-before.hs
@@ -0,0 +1,7 @@
+testPermParser :: Permutation Parser String
+testPermParser =
+  f <$> toPermutationWithDefault 'x' (char 'a')
+    <*> toPermutationWithDefault 'y' (char 'b')
+    <*> toPermutationWithDefault 'z' (char 'c')
+  where
+    f a b c = [a, b, c]
diff --git a/data/diff-tests/inputs/longer-v2.hs b/data/diff-tests/inputs/longer-v2.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/longer-v2.hs
@@ -0,0 +1,16 @@
+module Main (foo) where
+
+a
+b
+c
+
+main :: IO ()
+main = return ()
+
+d
+e
+f
+g
+
+foo :: Int
+foo = 6
diff --git a/data/diff-tests/inputs/longer.hs b/data/diff-tests/inputs/longer.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/longer.hs
@@ -0,0 +1,16 @@
+module Main (main) where
+
+a
+b
+c
+
+main :: IO ()
+main = return ()
+
+d
+e
+f
+g
+
+foo :: Int
+foo = 5
diff --git a/data/diff-tests/outputs/no-preceding.txt b/data/diff-tests/outputs/no-preceding.txt
--- a/data/diff-tests/outputs/no-preceding.txt
+++ b/data/diff-tests/outputs/no-preceding.txt
@@ -1,6 +1,7 @@
 TEST
-@@ -1,3 +1,3 @@
+@@ -1,4 +1,4 @@
 - module Main (foo) where
 + module Main (main) where
+
   main :: IO ()
   main = return ()
diff --git a/data/diff-tests/outputs/simple-hunk.txt b/data/diff-tests/outputs/simple-hunk.txt
--- a/data/diff-tests/outputs/simple-hunk.txt
+++ b/data/diff-tests/outputs/simple-hunk.txt
@@ -1,9 +1,10 @@
 TEST
-@@ -1,6 +1,6 @@
+@@ -1,7 +1,7 @@
   module Main (main) where
 
   main :: IO ()
 - main = return ()
 + main = pure ()
+
   foo :: Int
   foo = 5
diff --git a/data/diff-tests/outputs/trimming-trailing-both-eof.txt b/data/diff-tests/outputs/trimming-trailing-both-eof.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/trimming-trailing-both-eof.txt
@@ -0,0 +1,10 @@
+TEST
+@@ -1,6 +1,7 @@
+  testPermParser :: Permutation Parser String
+  testPermParser =
+-   f <$> toPermutationWithDefault 'x' (char 'a')
++   f
++     <$> toPermutationWithDefault 'x' (char 'a')
+      <*> toPermutationWithDefault 'y' (char 'b')
+      <*> toPermutationWithDefault 'z' (char 'c')
+    where
diff --git a/data/diff-tests/outputs/trimming-trailing-both-out-of-margin.txt b/data/diff-tests/outputs/trimming-trailing-both-out-of-margin.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/trimming-trailing-both-out-of-margin.txt
@@ -0,0 +1,13 @@
+TEST
+@@ -1,4 +1,4 @@
+- module Main (main) where
++ module Main (foo) where
+
+  a
+  b
+@@ -13,4 +13,4 @@
+  g
+
+  foo :: Int
+- foo = 5
++ foo = 6
diff --git a/data/examples/declaration/class/default-signatures-out.hs b/data/examples/declaration/class/default-signatures-out.hs
--- a/data/examples/declaration/class/default-signatures-out.hs
+++ b/data/examples/declaration/class/default-signatures-out.hs
@@ -14,9 +14,7 @@
     ( Read a,
       Semigroup a
     ) =>
-    a ->
-    a ->
-    a
+    a -> a -> a
   -- Even more pointless comment
   bar
     a
diff --git a/data/examples/declaration/class/default-signatures-simple-out.hs b/data/examples/declaration/class/default-signatures-simple-out.hs
--- a/data/examples/declaration/class/default-signatures-simple-out.hs
+++ b/data/examples/declaration/class/default-signatures-simple-out.hs
@@ -4,5 +4,5 @@
 class Foo a where
   -- | Foo
   foo :: a -> String
-  default foo :: Show a => a -> String
+  default foo :: (Show a) => a -> String
   foo = show
diff --git a/data/examples/declaration/class/super-classes-out.hs b/data/examples/declaration/class/super-classes-out.hs
--- a/data/examples/declaration/class/super-classes-out.hs
+++ b/data/examples/declaration/class/super-classes-out.hs
@@ -1,6 +1,6 @@
 class Foo a
 
-class Foo a => Bar a
+class (Foo a) => Bar a
 
 class
   (Foo a, Bar a) =>
diff --git a/data/examples/declaration/data/ctype-0-out.hs b/data/examples/declaration/data/ctype-0-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype-0-out.hs
@@ -0,0 +1,1 @@
+data {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/ctype-0.hs b/data/examples/declaration/data/ctype-0.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype-0.hs
@@ -0,0 +1,1 @@
+data    {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/ctype-1-out.hs b/data/examples/declaration/data/ctype-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype-1-out.hs
@@ -0,0 +1,6 @@
+data
+  {-# CTYPE "header.h" "an-ffi-type-with-along-name" #-}
+  AnFFITypeWithAlongName = AnFFITypeWithAlongName
+  { a :: X,
+    b :: Y
+  }
diff --git a/data/examples/declaration/data/ctype-1.hs b/data/examples/declaration/data/ctype-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype-1.hs
@@ -0,0 +1,6 @@
+data
+  {-# CTYPE "header.h" "an-ffi-type-with-along-name"  #-}
+  AnFFITypeWithAlongName = AnFFITypeWithAlongName
+  { a :: X,
+    b :: Y
+  }
diff --git a/data/examples/declaration/data/ctype-out.hs b/data/examples/declaration/data/ctype-out.hs
deleted file mode 100644
--- a/data/examples/declaration/data/ctype-out.hs
+++ /dev/null
@@ -1,1 +0,0 @@
-data {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/ctype.hs b/data/examples/declaration/data/ctype.hs
deleted file mode 100644
--- a/data/examples/declaration/data/ctype.hs
+++ /dev/null
@@ -1,1 +0,0 @@
-data    {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/datatype-contexts-out.hs b/data/examples/declaration/data/datatype-contexts-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/datatype-contexts-out.hs
@@ -0,0 +1,11 @@
+data (IsString s) => T s = T
+
+data
+  (IsString s) =>
+  T s = T
+
+data
+  ( IsString s,
+    IsString s
+  ) =>
+  T s = T
diff --git a/data/examples/declaration/data/datatype-contexts.hs b/data/examples/declaration/data/datatype-contexts.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/datatype-contexts.hs
@@ -0,0 +1,8 @@
+data IsString s => T s = T
+
+data IsString s =>
+  T s = T
+
+data
+  ( IsString s
+  , IsString s ) =>  T s = T
diff --git a/data/examples/declaration/data/existential-multiline-out.hs b/data/examples/declaration/data/existential-multiline-out.hs
--- a/data/examples/declaration/data/existential-multiline-out.hs
+++ b/data/examples/declaration/data/existential-multiline-out.hs
@@ -2,7 +2,7 @@
 
 data Foo
   = forall a. MkFoo a (a -> Bool)
-  | forall a. Eq a => MkBar a
+  | forall a. (Eq a) => MkBar a
 
 data Bar
   = forall x y.
diff --git a/data/examples/declaration/data/existential-out.hs b/data/examples/declaration/data/existential-out.hs
--- a/data/examples/declaration/data/existential-out.hs
+++ b/data/examples/declaration/data/existential-out.hs
@@ -2,4 +2,4 @@
 
 data Foo = forall a. MkFoo a (a -> Bool)
 
-data Bar = forall a b. a + b => Bar a b
+data Bar = forall a b. (a + b) => Bar a b
diff --git a/data/examples/declaration/data/existential-unicode-out.hs b/data/examples/declaration/data/existential-unicode-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/existential-unicode-out.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE UnicodeSyntax #-}
+
+data Foo
+  = forall a.
+    Foo
+
+data Bar
+  = forall a.
+    Bar
diff --git a/data/examples/declaration/data/existential-unicode.hs b/data/examples/declaration/data/existential-unicode.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/existential-unicode.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE UnicodeSyntax #-}
+
+data Foo = forall
+  a. Foo
+
+data Bar = ∀
+  a. Bar
diff --git a/data/examples/declaration/data/field-layout/record-0-out.hs b/data/examples/declaration/data/field-layout/record-0-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-0-out.hs
@@ -0,0 +1,10 @@
+-- | Foo.
+data Foo = Foo
+  { -- | Something
+    foo :: Foo Int Int,
+    -- | Something else
+    bar ::
+      Bar
+        Char
+        Char
+  }
diff --git a/data/examples/declaration/data/field-layout/record-0.hs b/data/examples/declaration/data/field-layout/record-0.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-0.hs
@@ -0,0 +1,9 @@
+-- | Foo.
+
+data Foo = Foo
+  { foo :: Foo Int Int
+    -- ^ Something
+  , bar :: Bar Char
+           Char
+    -- ^ Something else
+  }
diff --git a/data/examples/declaration/data/field-layout/record-1-out.hs b/data/examples/declaration/data/field-layout/record-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-1-out.hs
@@ -0,0 +1,11 @@
+-- | Foo.
+data Foo
+  = Foo
+  { -- | Something
+    foo :: Foo Int Int,
+    -- | Something else
+    bar ::
+      Bar
+        Char
+        Char
+  }
diff --git a/data/examples/declaration/data/field-layout/record-1.hs b/data/examples/declaration/data/field-layout/record-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-1.hs
@@ -0,0 +1,10 @@
+-- | Foo.
+
+data Foo =
+  Foo
+  { foo :: Foo Int Int
+    -- ^ Something
+  , bar :: Bar Char
+           Char
+    -- ^ Something else
+  }
diff --git a/data/examples/declaration/data/field-layout/record-2-out.hs b/data/examples/declaration/data/field-layout/record-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-2-out.hs
@@ -0,0 +1,7 @@
+data IndexWithInfo schema
+  = forall x.
+  IndexWithInfo
+  { checkedIndex :: Index schema x,
+    checkedIndexName :: U.Variable,
+    checkedIndexType :: Type x
+  }
diff --git a/data/examples/declaration/data/field-layout/record-2.hs b/data/examples/declaration/data/field-layout/record-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-2.hs
@@ -0,0 +1,7 @@
+data IndexWithInfo schema =
+  forall x.
+  IndexWithInfo
+    { checkedIndex :: Index schema x
+    , checkedIndexName :: U.Variable
+    , checkedIndexType :: Type x
+    }
diff --git a/data/examples/declaration/data/field-layout/record-out.hs b/data/examples/declaration/data/field-layout/record-out.hs
deleted file mode 100644
--- a/data/examples/declaration/data/field-layout/record-out.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module Main where
-
--- | Foo.
-data Foo = Foo
-  { -- | Something
-    foo :: Foo Int Int,
-    -- | Something else
-    bar ::
-      Bar
-        Char
-        Char
-  }
diff --git a/data/examples/declaration/data/field-layout/record.hs b/data/examples/declaration/data/field-layout/record.hs
deleted file mode 100644
--- a/data/examples/declaration/data/field-layout/record.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-module Main where
-
--- | Foo.
-
-data Foo = Foo
-  { foo :: Foo Int Int
-    -- ^ Something
-  , bar :: Bar Char
-           Char
-    -- ^ Something else
-  }
diff --git a/data/examples/declaration/data/gadt/multiline-out.hs b/data/examples/declaration/data/gadt/multiline-out.hs
--- a/data/examples/declaration/data/gadt/multiline-out.hs
+++ b/data/examples/declaration/data/gadt/multiline-out.hs
@@ -9,17 +9,11 @@
     forall a b.
     (Show a, Eq b) => -- foo
     -- bar
-    a ->
-    b ->
-    Foo 'Int
+    a -> b -> Foo 'Int
   -- | But 'Bar' is also not too bad.
   Bar ::
-    Int ->
-    Maybe Text ->
-    Foo 'Bool
+    Int -> Maybe Text -> Foo 'Bool
   -- | So is 'Baz'.
   Baz ::
-    forall a.
-    a ->
-    Foo 'String
+    forall a. a -> Foo 'String
   (:~>) :: Foo a -> Foo a -> Foo a
diff --git a/data/examples/declaration/data/gadt/multiline-where-out.hs b/data/examples/declaration/data/gadt/multiline-where-out.hs
--- a/data/examples/declaration/data/gadt/multiline-where-out.hs
+++ b/data/examples/declaration/data/gadt/multiline-where-out.hs
@@ -5,3 +5,26 @@
     c
   where
   Foo :: a -> b -> c -> Foo a b c
+
+data
+  Foo ::
+    Type ->
+    Type ->
+    Type
+  where
+  Foo :: Foo a b
+
+data
+  Foo a b c ::
+    Type -> Type -> Type -> Type
+  where
+  Foo :: Foo a b c
+
+data
+  Vec ::
+    Type ->
+    Type ->
+    Type
+  where
+  Nil :: Vec a Ze
+  Cons :: a -> Vec a n -> Vec a (Su n)
diff --git a/data/examples/declaration/data/gadt/multiline-where.hs b/data/examples/declaration/data/gadt/multiline-where.hs
--- a/data/examples/declaration/data/gadt/multiline-where.hs
+++ b/data/examples/declaration/data/gadt/multiline-where.hs
@@ -4,3 +4,20 @@
     b
     c where
   Foo :: a -> b -> c -> Foo a b c
+
+data Foo :: Type
+         -> Type
+         -> Type where
+  Foo :: Foo a b
+
+data Foo a b c ::
+  Type -> Type -> Type -> Type where
+  Foo :: Foo a b c
+
+data Vec
+  :: Type
+  -> Type
+  -> Type
+  where
+    Nil  :: Vec a Ze
+    Cons :: a -> Vec a n -> Vec a (Su n)
diff --git a/data/examples/declaration/data/gadt/multiple-declaration-out.hs b/data/examples/declaration/data/gadt/multiple-declaration-out.hs
--- a/data/examples/declaration/data/gadt/multiple-declaration-out.hs
+++ b/data/examples/declaration/data/gadt/multiple-declaration-out.hs
@@ -4,12 +4,10 @@
 data GADT1 a where
   GADT11,
     GADT12 ::
-    Int ->
-    GADT1 a
+    Int -> GADT1 a
 
 data GADT2 a where
   GADT21,
     GADT21,
     GADT22 ::
-    Int ->
-    GADT2 a
+    Int -> GADT2 a
diff --git a/data/examples/declaration/data/gadt/unicode-out.hs b/data/examples/declaration/data/gadt/unicode-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/gadt/unicode-out.hs
@@ -0,0 +1,4 @@
+{-# LANGUAGE UnicodeSyntax #-}
+
+data Foo :: Type -> Type where
+  Foo :: a -> Foo a
diff --git a/data/examples/declaration/data/gadt/unicode.hs b/data/examples/declaration/data/gadt/unicode.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/gadt/unicode.hs
@@ -0,0 +1,4 @@
+{-# LANGUAGE UnicodeSyntax #-}
+
+data Foo ∷ Type → Type where
+  Foo ∷ a → Foo a
diff --git a/data/examples/declaration/data/infix-haddocks-out.hs b/data/examples/declaration/data/infix-haddocks-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/infix-haddocks-out.hs
@@ -0,0 +1,51 @@
+{- https://github.com/tweag/ormolu/issues/758 -}
+
+data A
+  = -- | Docs for :#
+    A :# A
+
+data WithDocs
+  = forall left right.
+    (Show left) =>
+    -- | Docs for left arg
+    left
+      -- | Docs for op
+      :*:
+      -- | Docs for right arg
+      right
+
+data MixedDocs
+  = forall left right.
+    (Show left) =>
+    left -- ^ before
+      :*:
+      -- | after
+      right
+
+data DocPartial
+  = Left -- ^ left docs
+    -- on multiple
+    -- lines
+      :*:
+      Right
+  | -- | op
+    Left
+      :*:
+      Right
+  | Left
+      :*:
+      -- | right
+      Right
+  | -- | op
+    Left
+      :*:
+      Right
+  | -- | op
+    Left
+      :*:
+      Right
+
+data NoDocs
+  = Left
+      :*:
+      Right
diff --git a/data/examples/declaration/data/infix-haddocks.hs b/data/examples/declaration/data/infix-haddocks.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/infix-haddocks.hs
@@ -0,0 +1,43 @@
+{- https://github.com/tweag/ormolu/issues/758 -}
+
+data A = A :# A -- ^ Docs for :#
+
+data WithDocs
+  = forall left right.
+    Show left =>
+    left -- ^ Docs for left arg
+    :*: -- ^ Docs for op
+    right -- ^ Docs for right arg
+
+data MixedDocs
+  -- | before
+  = forall left right.
+    Show left =>
+    left :*: right
+  -- ^ after
+
+data DocPartial
+  = Left -- ^ left docs
+         -- on multiple
+         -- lines
+      :*: Right
+  | Left
+      :*: -- ^ op
+      Right
+  | Left
+      :*:
+      -- | right
+      Right
+  | -- | op
+    Left
+      :*:
+      Right
+  | Left
+      :*:
+      Right
+    -- ^ op
+
+data NoDocs
+  = Left
+    :*:
+    Right
diff --git a/data/examples/declaration/data/invisible-binders-out.hs b/data/examples/declaration/data/invisible-binders-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/invisible-binders-out.hs
@@ -0,0 +1,2 @@
+type T :: forall k. k -> forall j. j -> Type
+data T @k (a :: k) @(j :: Type) (b :: j)
diff --git a/data/examples/declaration/data/invisible-binders.hs b/data/examples/declaration/data/invisible-binders.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/invisible-binders.hs
@@ -0,0 +1,2 @@
+type T :: forall k. k -> forall j. j -> Type
+data T @k (a :: k) @(j :: Type) (b :: j)
diff --git a/data/examples/declaration/data/linear-out.hs b/data/examples/declaration/data/linear-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/linear-out.hs
@@ -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
diff --git a/data/examples/declaration/data/linear.hs b/data/examples/declaration/data/linear.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/linear.hs
@@ -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
diff --git a/data/examples/declaration/data/record-fancy-existential-out.hs b/data/examples/declaration/data/record-fancy-existential-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/record-fancy-existential-out.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE ExistentialQuantification #-}
+
+data Foo = forall a b. (Show a, Eq b) => Bar
+  { foo :: a,
+    bars :: b
+  }
diff --git a/data/examples/declaration/data/record-fancy-existential.hs b/data/examples/declaration/data/record-fancy-existential.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/record-fancy-existential.hs
@@ -0,0 +1,5 @@
+{-# LANGUAGE ExistentialQuantification #-}
+data Foo = forall a b . (Show a, Eq b) => Bar
+  { foo  :: a
+  , bars :: b
+  }
diff --git a/data/examples/declaration/data/record-out.hs b/data/examples/declaration/data/record-out.hs
--- a/data/examples/declaration/data/record-out.hs
+++ b/data/examples/declaration/data/record-out.hs
@@ -12,7 +12,7 @@
     fooGag,
     fooGog ::
       NonEmpty
-        ( Indentity
+        ( Identity
             Bool
         ),
     -- | Huh!
diff --git a/data/examples/declaration/data/record.hs b/data/examples/declaration/data/record.hs
--- a/data/examples/declaration/data/record.hs
+++ b/data/examples/declaration/data/record.hs
@@ -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
diff --git a/data/examples/declaration/data/required-type-arguments-out.hs b/data/examples/declaration/data/required-type-arguments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/required-type-arguments-out.hs
@@ -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)
diff --git a/data/examples/declaration/data/required-type-arguments.hs b/data/examples/declaration/data/required-type-arguments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/required-type-arguments.hs
@@ -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)
diff --git a/data/examples/declaration/data/simple-broken-out.hs b/data/examples/declaration/data/simple-broken-out.hs
--- a/data/examples/declaration/data/simple-broken-out.hs
+++ b/data/examples/declaration/data/simple-broken-out.hs
@@ -1,7 +1,8 @@
 module Main where
 
 -- | Here we go.
-data Foo = Foo {unFoo :: Int}
+data Foo
+  = Foo {unFoo :: Int}
   deriving (Eq)
 
 -- | And once again.
diff --git a/data/examples/declaration/data/single-constructor-with-haddock-out.hs b/data/examples/declaration/data/single-constructor-with-haddock-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/single-constructor-with-haddock-out.hs
@@ -0,0 +1,7 @@
+data FutureSumType
+  = -- | This is what the documentation for this type looks like.
+    -- It is pretty awkward that the @--@s aren't aligned.
+    SingleConstructor
+    { someRecordField :: Bool,
+      someOtherRecordField :: Int
+    }
diff --git a/data/examples/declaration/data/single-constructor-with-haddock.hs b/data/examples/declaration/data/single-constructor-with-haddock.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/single-constructor-with-haddock.hs
@@ -0,0 +1,6 @@
+data FutureSumType = -- | This is what the documentation for this type looks like.
+  -- It is pretty awkward that the @--@s aren't aligned.
+  SingleConstructor
+  { someRecordField :: Bool,
+    someOtherRecordField :: Int
+  }
diff --git a/data/examples/declaration/data/type-data-out.hs b/data/examples/declaration/data/type-data-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/type-data-out.hs
@@ -0,0 +1,8 @@
+type data Universe = Character | Number | Boolean
+
+type data Maybe a
+  = Just a
+  | Nothing
+
+type data P :: Type -> Type -> Type where
+  MkP :: (a ~ Natural, b ~~ Char) => P a b
diff --git a/data/examples/declaration/data/type-data.hs b/data/examples/declaration/data/type-data.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/type-data.hs
@@ -0,0 +1,7 @@
+type data Universe = Character | Number | Boolean
+
+type data Maybe a = Just a
+                  | Nothing
+
+type data P :: Type -> Type -> Type where
+  MkP :: (a ~ Natural, b ~~ Char) => P a b
diff --git a/data/examples/declaration/data/unnamed-field-comment-0-out.hs b/data/examples/declaration/data/unnamed-field-comment-0-out.hs
--- a/data/examples/declaration/data/unnamed-field-comment-0-out.hs
+++ b/data/examples/declaration/data/unnamed-field-comment-0-out.hs
@@ -1,7 +1,7 @@
 data Foo
   = -- | Bar
     Bar
+      -- | Field 1
       Field1
-      -- ^ Field 1
+      -- | Field 2
       Field2
-      -- ^ Field 2
diff --git a/data/examples/declaration/data/unnamed-field-comment-1-out.hs b/data/examples/declaration/data/unnamed-field-comment-1-out.hs
--- a/data/examples/declaration/data/unnamed-field-comment-1-out.hs
+++ b/data/examples/declaration/data/unnamed-field-comment-1-out.hs
@@ -1,5 +1,5 @@
 data X
   = B
+      -- | y
       !Int
-      -- ^ y
       C
diff --git a/data/examples/declaration/data/unnamed-field-comment-2-out.hs b/data/examples/declaration/data/unnamed-field-comment-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/unnamed-field-comment-2-out.hs
@@ -0,0 +1,11 @@
+-- | Describes what sort of dictionary to generate for type class instances
+data Evidence
+  = -- | An existing named instance
+    NamedInstance (Qualified Ident)
+  | -- | Computed instances
+    WarnInstance
+      -- | Warn type class with a user-defined warning message
+      SourceType
+  | -- | The IsSymbol type class for a given Symbol literal
+    IsSymbolInstance PSString
+  deriving (Show, Eq)
diff --git a/data/examples/declaration/data/unnamed-field-comment-2.hs b/data/examples/declaration/data/unnamed-field-comment-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/unnamed-field-comment-2.hs
@@ -0,0 +1,9 @@
+-- | Describes what sort of dictionary to generate for type class instances
+data Evidence
+  -- | An existing named instance
+  = NamedInstance (Qualified Ident)
+
+  -- | Computed instances
+  | WarnInstance SourceType -- ^ Warn type class with a user-defined warning message
+  | IsSymbolInstance PSString -- ^ The IsSymbol type class for a given Symbol literal
+  deriving (Show, Eq)
diff --git a/data/examples/declaration/data/unnamed-field-comment-3-out.hs b/data/examples/declaration/data/unnamed-field-comment-3-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/unnamed-field-comment-3-out.hs
@@ -0,0 +1,5 @@
+data A
+  = A
+      -- | a number
+      Int
+      Bool
diff --git a/data/examples/declaration/data/unnamed-field-comment-3.hs b/data/examples/declaration/data/unnamed-field-comment-3.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/unnamed-field-comment-3.hs
@@ -0,0 +1,1 @@
+data A = A {- | a number -} Int Bool
diff --git a/data/examples/declaration/data/wildcard-binders-out.hs b/data/examples/declaration/data/wildcard-binders-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/wildcard-binders-out.hs
@@ -0,0 +1,1 @@
+data Proxy _ = Proxy
diff --git a/data/examples/declaration/data/wildcard-binders.hs b/data/examples/declaration/data/wildcard-binders.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/wildcard-binders.hs
@@ -0,0 +1,1 @@
+data Proxy _ = Proxy
diff --git a/data/examples/declaration/default/default-out.hs b/data/examples/declaration/default/default-out.hs
--- a/data/examples/declaration/default/default-out.hs
+++ b/data/examples/declaration/default/default-out.hs
@@ -1,3 +1,5 @@
+module MyModule (default Monoid) where
+
 default (Int, Foo, Bar)
 
 default
@@ -5,3 +7,9 @@
     Foo,
     Bar
   )
+
+default Num (Int, Float)
+
+default IsList ([], Vector)
+
+default IsString (Text.Text, Foundation.String, String)
diff --git a/data/examples/declaration/default/default.hs b/data/examples/declaration/default/default.hs
--- a/data/examples/declaration/default/default.hs
+++ b/data/examples/declaration/default/default.hs
@@ -1,6 +1,13 @@
+module MyModule (default Monoid) where
+
 default        (  Int , Foo     , Bar      )
 
 default ( Int
                , Foo,
   Bar
            )
+
+default Num (Int, Float)
+default IsList ([], Vector)
+
+default IsString (Text.Text, Foundation.String, String)
diff --git a/data/examples/declaration/foreign/foreign-import-multiline-out.hs b/data/examples/declaration/foreign/foreign-import-multiline-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/foreign/foreign-import-multiline-out.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE MultilineStrings #-}
+
+foreign import capi
+  """
+  foo
+     bar
+  """
+  foo :: Int -> Int
diff --git a/data/examples/declaration/foreign/foreign-import-multiline.hs b/data/examples/declaration/foreign/foreign-import-multiline.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/foreign/foreign-import-multiline.hs
@@ -0,0 +1,6 @@
+{-# language MultilineStrings #-}
+
+foreign import capi """
+         foo
+            bar
+     """ foo :: Int -> Int
diff --git a/data/examples/declaration/instance/contexts-out.hs b/data/examples/declaration/instance/contexts-out.hs
--- a/data/examples/declaration/instance/contexts-out.hs
+++ b/data/examples/declaration/instance/contexts-out.hs
@@ -1,4 +1,4 @@
-instance Eq a => Eq [a] where (==) _ _ = False
+instance (Eq a) => Eq [a] where (==) _ _ = False
 
 instance
   ( Ord a,
diff --git a/data/examples/declaration/instance/newlines-between-methods-out.hs b/data/examples/declaration/instance/newlines-between-methods-out.hs
--- a/data/examples/declaration/instance/newlines-between-methods-out.hs
+++ b/data/examples/declaration/instance/newlines-between-methods-out.hs
@@ -1,4 +1,4 @@
-instance Num a => Num (Diff a) where
+instance (Num a) => Num (Diff a) where
   D u dudx + D v dvdx = D (u + v) (dudx + dvdx)
   D u dudx - D v dvdx = D (u - v) (dudx - dvdx)
   D u dudx * D v dvdx = D (u * v) (u * dvdx + v * dudx)
diff --git a/data/examples/declaration/rewrite-rule/never-active-out.hs b/data/examples/declaration/rewrite-rule/never-active-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/rewrite-rule/never-active-out.hs
@@ -0,0 +1,1 @@
+{-# RULES "map-loop" [~] forall f. map' f = map' (id . f) #-}
diff --git a/data/examples/declaration/rewrite-rule/never-active.hs b/data/examples/declaration/rewrite-rule/never-active.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/rewrite-rule/never-active.hs
@@ -0,0 +1,1 @@
+{-# RULES "map-loop" [ ~  ]  forall f . map' f = map' (id . f) #-}
diff --git a/data/examples/declaration/rewrite-rule/prelude2-out.hs b/data/examples/declaration/rewrite-rule/prelude2-out.hs
--- a/data/examples/declaration/rewrite-rule/prelude2-out.hs
+++ b/data/examples/declaration/rewrite-rule/prelude2-out.hs
@@ -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
diff --git a/data/examples/declaration/rewrite-rule/prelude2.hs b/data/examples/declaration/rewrite-rule/prelude2.hs
--- a/data/examples/declaration/rewrite-rule/prelude2.hs
+++ b/data/examples/declaration/rewrite-rule/prelude2.hs
@@ -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
diff --git a/data/examples/declaration/rewrite-rule/type-signature-out.hs b/data/examples/declaration/rewrite-rule/type-signature-out.hs
--- a/data/examples/declaration/rewrite-rule/type-signature-out.hs
+++ b/data/examples/declaration/rewrite-rule/type-signature-out.hs
@@ -8,9 +8,7 @@
   z
   ( g ::
       forall b.
-      (a -> b -> b) ->
-      b ->
-      b
+      (a -> b -> b) -> b -> b
   ).
   foldr k z (build g) =
     g k z
diff --git a/data/examples/declaration/signature/fixity/infix-out.hs b/data/examples/declaration/signature/fixity/infix-out.hs
--- a/data/examples/declaration/signature/fixity/infix-out.hs
+++ b/data/examples/declaration/signature/fixity/infix-out.hs
@@ -1,3 +1,9 @@
 infix 0 <?>
 
 infix 9 <^-^>
+
+infix 2 ->
+
+infix 0 type <!>
+
+infix 9 +
diff --git a/data/examples/declaration/signature/fixity/infix.hs b/data/examples/declaration/signature/fixity/infix.hs
--- a/data/examples/declaration/signature/fixity/infix.hs
+++ b/data/examples/declaration/signature/fixity/infix.hs
@@ -1,2 +1,8 @@
 infix 0 <?>
 infix 9 <^-^>
+
+infix 2 ->
+
+infix 0 type <!>
+
+infix +
diff --git a/data/examples/declaration/signature/fixity/infixl-out.hs b/data/examples/declaration/signature/fixity/infixl-out.hs
--- a/data/examples/declaration/signature/fixity/infixl-out.hs
+++ b/data/examples/declaration/signature/fixity/infixl-out.hs
@@ -1,3 +1,5 @@
 infixl 8 ***
 
 infixl 0 $, *, +, &&, **
+
+infixl 9 type $
diff --git a/data/examples/declaration/signature/fixity/infixl.hs b/data/examples/declaration/signature/fixity/infixl.hs
--- a/data/examples/declaration/signature/fixity/infixl.hs
+++ b/data/examples/declaration/signature/fixity/infixl.hs
@@ -1,2 +1,4 @@
 infixl 8 ***
 infixl 0 $, *, +, &&, **
+
+infixl 9 type $
diff --git a/data/examples/declaration/signature/fixity/infixr-out.hs b/data/examples/declaration/signature/fixity/infixr-out.hs
--- a/data/examples/declaration/signature/fixity/infixr-out.hs
+++ b/data/examples/declaration/signature/fixity/infixr-out.hs
@@ -1,3 +1,5 @@
 infixr 8 `Foo`
 
 infixr 0 ***, &&&
+
+infixr 0 data $
diff --git a/data/examples/declaration/signature/fixity/infixr.hs b/data/examples/declaration/signature/fixity/infixr.hs
--- a/data/examples/declaration/signature/fixity/infixr.hs
+++ b/data/examples/declaration/signature/fixity/infixr.hs
@@ -1,2 +1,4 @@
 infixr 8 `Foo`
 infixr 0 ***, &&&
+
+infixr 0 data $
diff --git a/data/examples/declaration/signature/inline/noinline-out.hs b/data/examples/declaration/signature/inline/noinline-out.hs
--- a/data/examples/declaration/signature/inline/noinline-out.hs
+++ b/data/examples/declaration/signature/inline/noinline-out.hs
@@ -9,3 +9,7 @@
 baz :: Int -> Int
 baz = id
 {-# NOINLINE [~2] baz #-}
+
+blub :: Int -> Int
+blub = baz
+{-# OPAQUE blub #-}
diff --git a/data/examples/declaration/signature/inline/noinline.hs b/data/examples/declaration/signature/inline/noinline.hs
--- a/data/examples/declaration/signature/inline/noinline.hs
+++ b/data/examples/declaration/signature/inline/noinline.hs
@@ -11,3 +11,7 @@
 baz = id
 
 {-#   NOINLINE    [~2] baz #-}
+
+blub :: Int -> Int
+blub = baz
+{-# opaque blub #-}
diff --git a/data/examples/declaration/signature/specialize/specialize-2-out.hs b/data/examples/declaration/signature/specialize/specialize-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/specialize/specialize-2-out.hs
@@ -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)
+  #-}
diff --git a/data/examples/declaration/signature/specialize/specialize-2.hs b/data/examples/declaration/signature/specialize/specialize-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/specialize/specialize-2.hs
@@ -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) #-}
diff --git a/data/examples/declaration/signature/specialize/specialize-3-out.hs b/data/examples/declaration/signature/specialize/specialize-3-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/specialize/specialize-3-out.hs
@@ -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 #-}
diff --git a/data/examples/declaration/signature/specialize/specialize-3.hs b/data/examples/declaration/signature/specialize/specialize-3.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/specialize/specialize-3.hs
@@ -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 #-}
diff --git a/data/examples/declaration/signature/specialize/specialize-instance-out.hs b/data/examples/declaration/signature/specialize/specialize-instance-out.hs
--- a/data/examples/declaration/signature/specialize/specialize-instance-out.hs
+++ b/data/examples/declaration/signature/specialize/specialize-instance-out.hs
@@ -8,7 +8,6 @@
 
 instance (Num r, V.Vector v r, Factored m r) => Num (VT v m r) where
   {-# SPECIALIZE instance
-    ( Factored m Int => Num (VT U.Vector m Int)
-    )
+    ((Factored m Int) => Num (VT U.Vector m Int))
     #-}
   VT x + VT y = VT $ V.zipWith (+) x y
diff --git a/data/examples/declaration/signature/specialize/specialize-out.hs b/data/examples/declaration/signature/specialize/specialize-out.hs
--- a/data/examples/declaration/signature/specialize/specialize-out.hs
+++ b/data/examples/declaration/signature/specialize/specialize-out.hs
@@ -1,13 +1,13 @@
-foo :: Num a => a -> a
+foo :: (Num a) => a -> a
 foo = id
 {-# SPECIALIZE foo :: Int -> Int #-}
 {-# SPECIALIZE INLINE foo :: Float -> Float #-}
 
 {-# SPECIALIZE NOINLINE [2] bar :: Int -> Int #-}
-bar :: Num a => a -> a
+bar :: (Num a) => a -> a
 bar = id
 
-baz :: Num a => a -> a
+baz :: (Num a) => a -> a
 baz = id
 {-# SPECIALIZE [~2] baz ::
   Int ->
@@ -20,5 +20,5 @@
   Bool,
   Integer -> Bool
   #-}
-fits13Bits :: Integral a => a -> Bool
+fits13Bits :: (Integral a) => a -> Bool
 fits13Bits x = x >= -4096 && x < 4096
diff --git a/data/examples/declaration/signature/type/unicode-out.hs b/data/examples/declaration/signature/type/unicode-out.hs
--- a/data/examples/declaration/signature/type/unicode-out.hs
+++ b/data/examples/declaration/signature/type/unicode-out.hs
@@ -1,4 +1,4 @@
 {-# LANGUAGE UnicodeSyntax #-}
 
-foo :: forall a. Show a => a -> String
+foo :: forall a. (Show a) => a -> String
 foo = const ()
diff --git a/data/examples/declaration/splice/bracket-unicode-out.hs b/data/examples/declaration/splice/bracket-unicode-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/splice/bracket-unicode-out.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+ascii = [|x|]
+
+unicode = [|x|]
diff --git a/data/examples/declaration/splice/bracket-unicode.hs b/data/examples/declaration/splice/bracket-unicode.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/splice/bracket-unicode.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE UnicodeSyntax   #-}
+
+ascii = [|x|]
+
+unicode = ⟦x⟧
diff --git a/data/examples/declaration/splice/quasiquote-out.hs b/data/examples/declaration/splice/quasiquote-out.hs
--- a/data/examples/declaration/splice/quasiquote-out.hs
+++ b/data/examples/declaration/splice/quasiquote-out.hs
@@ -10,3 +10,7 @@
 [d|    foo bar
 
 |]
+
+header =
+  [here|
+#include foo|]
diff --git a/data/examples/declaration/splice/quasiquote.hs b/data/examples/declaration/splice/quasiquote.hs
--- a/data/examples/declaration/splice/quasiquote.hs
+++ b/data/examples/declaration/splice/quasiquote.hs
@@ -9,3 +9,6 @@
 [d|    foo bar
 
 |]
+
+header = [here|
+#include foo|]
diff --git a/data/examples/declaration/splice/typed-splice-out.hs b/data/examples/declaration/splice/typed-splice-out.hs
--- a/data/examples/declaration/splice/typed-splice-out.hs
+++ b/data/examples/declaration/splice/typed-splice-out.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE TemplateHaskell #-}
 
 x =
-  $$( foo bar
-    )
+  $$(foo bar)
 
 x = $$foo
diff --git a/data/examples/declaration/type-families/closed-type-family/multi-line-out.hs b/data/examples/declaration/type-families/closed-type-family/multi-line-out.hs
--- a/data/examples/declaration/type-families/closed-type-family/multi-line-out.hs
+++ b/data/examples/declaration/type-families/closed-type-family/multi-line-out.hs
@@ -23,3 +23,8 @@
   F Bool =
     Char
   F a = String
+
+type family F a where
+  F a -- foo
+    =
+    a
diff --git a/data/examples/declaration/type-families/closed-type-family/multi-line.hs b/data/examples/declaration/type-families/closed-type-family/multi-line.hs
--- a/data/examples/declaration/type-families/closed-type-family/multi-line.hs
+++ b/data/examples/declaration/type-families/closed-type-family/multi-line.hs
@@ -12,3 +12,7 @@
   F Bool =
            Char
   F a    = String
+
+type family F a where
+  F a -- foo
+    = a
diff --git a/data/examples/declaration/type-families/closed-type-family/promotion-out.hs b/data/examples/declaration/type-families/closed-type-family/promotion-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/promotion-out.hs
@@ -0,0 +1,2 @@
+type family Foo a where
+  Foo '( 'x', a) = a
diff --git a/data/examples/declaration/type-families/closed-type-family/promotion.hs b/data/examples/declaration/type-families/closed-type-family/promotion.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/promotion.hs
@@ -0,0 +1,2 @@
+type family Foo a where
+  Foo '( 'x', a) = a
diff --git a/data/examples/declaration/type-families/closed-type-family/with-comments-out.hs b/data/examples/declaration/type-families/closed-type-family/with-comments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/with-comments-out.hs
@@ -0,0 +1,10 @@
+type family LT a b where
+  -- 0
+  LT 0 _ = True
+  -- 1
+  LT 1 0 = False
+  LT 1 _ = True
+  -- 2
+  LT 2 0 = False
+  LT 2 1 = False
+  LT 2 _ = True
diff --git a/data/examples/declaration/type-families/closed-type-family/with-comments.hs b/data/examples/declaration/type-families/closed-type-family/with-comments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/with-comments.hs
@@ -0,0 +1,12 @@
+type family LT a b where
+  -- 0
+  LT 0 _ = True
+
+  -- 1
+  LT 1 0 = False
+  LT 1 _ = True
+
+  -- 2
+  LT 2 0 = False
+  LT 2 1 = False
+  LT 2 _ = True
diff --git a/data/examples/declaration/type-synonyms/multi-line-out.hs b/data/examples/declaration/type-synonyms/multi-line-out.hs
--- a/data/examples/declaration/type-synonyms/multi-line-out.hs
+++ b/data/examples/declaration/type-synonyms/multi-line-out.hs
@@ -1,3 +1,5 @@
+import Servant.API
+
 type Foo a b c =
   Bar c a b
 
@@ -16,3 +18,7 @@
   "route1" :> ApiRoute1
     :<|> "route2" :> ApiRoute2 -- comment here
     :<|> OmitDocs :> "i" :> ASomething API
+
+type A -- foo
+  =
+  B
diff --git a/data/examples/declaration/type-synonyms/multi-line.hs b/data/examples/declaration/type-synonyms/multi-line.hs
--- a/data/examples/declaration/type-synonyms/multi-line.hs
+++ b/data/examples/declaration/type-synonyms/multi-line.hs
@@ -1,3 +1,5 @@
+import Servant.API
+
 type Foo a b c
   = Bar c a b
 
@@ -15,3 +17,6 @@
   = "route1" :> ApiRoute1
       :<|> "route2" :> ApiRoute2 -- comment here
       :<|> OmitDocs :> "i" :> ASomething API
+
+type A -- foo
+  = B
diff --git a/data/examples/declaration/type/parens-comments-out.hs b/data/examples/declaration/type/parens-comments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/parens-comments-out.hs
@@ -0,0 +1,4 @@
+type Test =
+  ( -- a
+    x
+  )
diff --git a/data/examples/declaration/type/parens-comments.hs b/data/examples/declaration/type/parens-comments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/parens-comments.hs
@@ -0,0 +1,3 @@
+type Test = (
+  -- a
+  x)
diff --git a/data/examples/declaration/type/promotion-1-out.hs b/data/examples/declaration/type/promotion-1-out.hs
--- a/data/examples/declaration/type/promotion-1-out.hs
+++ b/data/examples/declaration/type/promotion-1-out.hs
@@ -13,3 +13,7 @@
 type E = TypeError ('Text "Some text")
 
 type G = '[ '( 'Just, 'Bool)]
+
+type X = () '`PromotedInfix` ()
+
+type A = '[ 'a']
diff --git a/data/examples/declaration/type/promotion-1.hs b/data/examples/declaration/type/promotion-1.hs
--- a/data/examples/declaration/type/promotion-1.hs
+++ b/data/examples/declaration/type/promotion-1.hs
@@ -7,3 +7,7 @@
 
 type E = TypeError ('Text "Some text")
 type G = '[ '( 'Just, 'Bool) ]
+
+type X = () '`PromotedInfix` ()
+
+type A = '[ 'a' ]
diff --git a/data/examples/declaration/type/promotion-no-puns-out.hs b/data/examples/declaration/type/promotion-no-puns-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-no-puns-out.hs
@@ -0,0 +1,5 @@
+{-# LANGUAGE NoListTuplePuns #-}
+
+type X = (Int, String)
+
+type Y = [String, Int]
diff --git a/data/examples/declaration/type/promotion-no-puns.hs b/data/examples/declaration/type/promotion-no-puns.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-no-puns.hs
@@ -0,0 +1,5 @@
+{-# Language NoListTuplePuns #-}
+
+type X = (Int, String)
+
+type Y = [String, Int]
diff --git a/data/examples/declaration/type/wildcard-binders-out.hs b/data/examples/declaration/type/wildcard-binders-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/wildcard-binders-out.hs
@@ -0,0 +1,1 @@
+type Const a _ = a
diff --git a/data/examples/declaration/type/wildcard-binders.hs b/data/examples/declaration/type/wildcard-binders.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/wildcard-binders.hs
@@ -0,0 +1,1 @@
+type Const a _ = a
diff --git a/data/examples/declaration/value/function/application-1-out.hs b/data/examples/declaration/value/function/application-1-out.hs
--- a/data/examples/declaration/value/function/application-1-out.hs
+++ b/data/examples/declaration/value/function/application-1-out.hs
@@ -1,19 +1,19 @@
 main =
   do
-    x
-    y
-   z
+      x
+      y
+    z
 
 main =
   case foo of
-    x -> a
-   foo
-   a
-   b
+      x -> a
+    foo
+    a
+    b
 
 main =
   do
-    if x then y else z
-   foo
-   a
-   b
+      if x then y else z
+    foo
+    a
+    b
diff --git a/data/examples/declaration/value/function/application-2-out.hs b/data/examples/declaration/value/function/application-2-out.hs
--- a/data/examples/declaration/value/function/application-2-out.hs
+++ b/data/examples/declaration/value/function/application-2-out.hs
@@ -1,13 +1,11 @@
 {-# LANGUAGE TemplateHaskell #-}
 
 foo = do
-  $( bar
-   )
+  $(bar)
     baz
 
 foo = do
-  $$( bar
-    )
+  $$(bar)
     baz
 
 foo = do
@@ -16,8 +14,8 @@
 
 foo = do
   do
-    (+ 1)
-   2
+      (+ 1)
+    2
 
 foo = do
   case () of () -> (+ 1)
@@ -25,8 +23,8 @@
 
 foo = do
   case () of
-    () -> (+ 1)
-   2
+      () -> (+ 1)
+    2
 
 foo = do
   \case 2 -> 3
@@ -34,5 +32,5 @@
 
 foo = do
   \case
-    2 -> 3
-   2
+      2 -> 3
+    2
diff --git a/data/examples/declaration/value/function/arrow/proc-applications2-out.hs b/data/examples/declaration/value/function/arrow/proc-applications2-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-applications2-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-applications2-out.hs
@@ -8,4 +8,5 @@
       LT -> \a -> returnA -< x + a
       EQ -> \b -> returnA -< y + z + b
       GT -> \c -> returnA -< z + x
-  ) 1
+  )
+    1
diff --git a/data/examples/declaration/value/function/arrow/proc-do-complex-out.hs b/data/examples/declaration/value/function/arrow/proc-do-complex-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-complex-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-complex-out.hs
@@ -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,7 +29,8 @@
           Left
             ( z,
               w
-              ) -> \u -> -- Procs can have lambdas
+              ) -> \u ->
+              -- Procs can have lambdas
               let v =
                     u -- Actually never used
                       ^ 2
@@ -58,5 +59,8 @@
             -<
               ( i
                   + x
-                  * y -- Just do the calculation
+                    * y -- Just do the calculation
               )
+
+traverseA_ f = proc (e, (xs, s)) ->
+  (| foldlA' (\() x -> do (e, (x, s)) >- f; () >- returnA) |) () xs
diff --git a/data/examples/declaration/value/function/arrow/proc-do-complex.hs b/data/examples/declaration/value/function/arrow/proc-do-complex.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-complex.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-complex.hs
@@ -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
@@ -47,3 +47,6 @@
           returnA -< (i +
                       x *
                       y) -- Just do the calculation
+
+traverseA_ f = proc (e, (xs, s)) ->
+  (| foldlA' (\() x -> do { (e, (x, s)) >- f; () >- returnA }) |) () xs
diff --git a/data/examples/declaration/value/function/arrow/proc-do-simple1-out.hs b/data/examples/declaration/value/function/arrow/proc-do-simple1-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-simple1-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-simple1-out.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE Arrows #-}
 
-bar f = proc a -> do
-  b <- f -< a
+bar f = proc a -> do b <- f -< a
 
 barbar f g = proc a -> do
   b <- f -< a
@@ -16,3 +15,15 @@
     f
       -<
         a
+
+foo =
+  proc x ->
+    do
+        returnA -< x
+      1
+
+foo a =
+  proc x ->
+    case Left x of
+        Left x -> returnA -< x
+      a
diff --git a/data/examples/declaration/value/function/arrow/proc-do-simple1.hs b/data/examples/declaration/value/function/arrow/proc-do-simple1.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-simple1.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-simple1.hs
@@ -18,3 +18,13 @@
        -<
        a
 
+foo =
+  proc x -> do
+    returnA -< x
+   1
+
+foo a =
+  proc x ->
+    case Left x of
+      Left x -> returnA -< x
+     a
diff --git a/data/examples/declaration/value/function/arrow/proc-do-simple2-out.hs b/data/examples/declaration/value/function/arrow/proc-do-simple2-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-simple2-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-simple2-out.hs
@@ -1,7 +1,6 @@
 {-# LANGUAGE Arrows #-}
 
-foo f = proc a -> do
-  f -< a
+foo f = proc a -> do f -< a
 
 bazbaz f g h = proc (a, b, c) -> do
   x <-
@@ -20,3 +19,5 @@
   returnA
     -<
       (x, y, z)
+
+bar f = proc x -> do { f -< x } <+> do f -< x
diff --git a/data/examples/declaration/value/function/arrow/proc-do-simple2.hs b/data/examples/declaration/value/function/arrow/proc-do-simple2.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-simple2.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-simple2.hs
@@ -18,3 +18,5 @@
        )
      returnA -<
        (x, y, z)
+
+bar f = proc x -> do {f -< x} <+> do f -< x
diff --git a/data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs b/data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs
@@ -10,4 +10,12 @@
   (|
     bar
       (bindA -< y)
-    |) z
+    |)
+    z
+
+foo2 = proc () -> do
+  ( proc () ->
+      returnA -< ()
+    )
+    -<
+      ()
diff --git a/data/examples/declaration/value/function/arrow/proc-form-do-indent.hs b/data/examples/declaration/value/function/arrow/proc-form-do-indent.hs
--- a/data/examples/declaration/value/function/arrow/proc-form-do-indent.hs
+++ b/data/examples/declaration/value/function/arrow/proc-form-do-indent.hs
@@ -11,3 +11,8 @@
     bar
       (bindA -< y)
     |) z
+
+foo2 = proc () -> do
+  (proc () ->
+    returnA -< ()
+    ) -< ()
diff --git a/data/examples/declaration/value/function/arrow/proc-forms2-out.hs b/data/examples/declaration/value/function/arrow/proc-forms2-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-forms2-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-forms2-out.hs
@@ -15,27 +15,32 @@
     ( (h f . h g)
         -<
           (y x) . y z
-    ) |||
-      ( (h g . h f)
-          -<
-            y z . (y x)
-      )
+    )
+      ||| ( (h g . h f)
+              -<
+                y z . (y x)
+          )
 
 bar4 = proc x ->
   case x of
     Just f -> f -< ()
     Nothing -> x -< ()
-  <+> do
-    g -< x
+    <+> do
+      g -< x
 
 expr' = proc x ->
   do
     returnA -< x
-  <+> do
-    symbol Plus -< ()
-    y <- term -< ()
-    expr' -< x + y
-  <+> do
-    symbol Minus -< ()
-    y <- term -< ()
-    expr' -< x - y
+    <+> do
+      symbol Plus -< ()
+      y <- term -< ()
+      expr' -< x + y
+    <+> do
+      symbol Minus -< ()
+      y <- term -< ()
+      expr' -< x - y
+
+bar f = proc (a, b) -> do
+  (f a -< b)
+    >-> (\y -> f b >- a)
+    >-> (\y -> f b >- a)
diff --git a/data/examples/declaration/value/function/arrow/proc-forms2.hs b/data/examples/declaration/value/function/arrow/proc-forms2.hs
--- a/data/examples/declaration/value/function/arrow/proc-forms2.hs
+++ b/data/examples/declaration/value/function/arrow/proc-forms2.hs
@@ -34,3 +34,8 @@
                 symbol Minus -< ()
                 y <- term -< ()
                 expr' -< x - y
+
+bar f = proc (a, b) -> do
+    (f a -< b)
+      >-> (\y -> f b >- a)
+      >-> (\y -> f b >- a)
diff --git a/data/examples/declaration/value/function/arrow/proc-lambdas-out.hs b/data/examples/declaration/value/function/arrow/proc-lambdas-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-lambdas-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-lambdas-out.hs
@@ -5,5 +5,6 @@
 bar =
   proc x -> \f g h ->
     \() ->
-      \(Left (x, y)) -> -- Tuple value
+      \(Left (x, y)) ->
+        -- Tuple value
         f (g (h x)) -< y
diff --git a/data/examples/declaration/value/function/arrow/proc-parentheses-out.hs b/data/examples/declaration/value/function/arrow/proc-parentheses-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-parentheses-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-parentheses-out.hs
@@ -4,8 +4,7 @@
 
 bar f g = proc a ->
   ( ( (f)
-        ( g
-        )
+        (g)
     )
       -<
         ( ( ( ( ( ( g
diff --git a/data/examples/declaration/value/function/block-arguments-out.hs b/data/examples/declaration/value/function/block-arguments-out.hs
--- a/data/examples/declaration/value/function/block-arguments-out.hs
+++ b/data/examples/declaration/value/function/block-arguments-out.hs
@@ -32,3 +32,5 @@
   do a
   do b
   do c
+
+f11 = do a do b do c do d
diff --git a/data/examples/declaration/value/function/block-arguments.hs b/data/examples/declaration/value/function/block-arguments.hs
--- a/data/examples/declaration/value/function/block-arguments.hs
+++ b/data/examples/declaration/value/function/block-arguments.hs
@@ -28,3 +28,5 @@
   do { a }
   do { b }
   do c
+
+f11 = do a do b do c do d
diff --git a/data/examples/declaration/value/function/case-empty-out.hs b/data/examples/declaration/value/function/case-empty-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/case-empty-out.hs
@@ -0,0 +1,6 @@
+absurd x = case x of {}
+absurd = \case {}
+absurd x = case x of {}
+absurd = \case {}
+
+foo = case () of {} 1
diff --git a/data/examples/declaration/value/function/case-empty.hs b/data/examples/declaration/value/function/case-empty.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/case-empty.hs
@@ -0,0 +1,6 @@
+absurd x = case x of
+absurd = \case
+absurd x = case x of {}
+absurd = \case {}
+
+foo = case () of {} 1
diff --git a/data/examples/declaration/value/function/case-multi-line-guards-out.hs b/data/examples/declaration/value/function/case-multi-line-guards-out.hs
--- a/data/examples/declaration/value/function/case-multi-line-guards-out.hs
+++ b/data/examples/declaration/value/function/case-multi-line-guards-out.hs
@@ -3,8 +3,8 @@
   case x of
     x
       | x > 10 ->
-        foo
-          + bar
+          foo
+            + bar
     x | x > 5 -> 10
     _ -> 20
 
diff --git a/data/examples/declaration/value/function/case-single-line-with-braces-out.hs b/data/examples/declaration/value/function/case-single-line-with-braces-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/case-single-line-with-braces-out.hs
@@ -0,0 +1,2 @@
+getValue :: Maybe Int -> Int
+getValue x = case x of Just n -> n; Nothing -> 0
diff --git a/data/examples/declaration/value/function/case-single-line-with-braces.hs b/data/examples/declaration/value/function/case-single-line-with-braces.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/case-single-line-with-braces.hs
@@ -0,0 +1,2 @@
+getValue :: Maybe Int -> Int
+getValue x = case x of {Just n -> n; Nothing -> 0}
diff --git a/data/examples/declaration/value/function/comprehension/transform-monad-out.hs b/data/examples/declaration/value/function/comprehension/transform-monad-out.hs
--- a/data/examples/declaration/value/function/comprehension/transform-monad-out.hs
+++ b/data/examples/declaration/value/function/comprehension/transform-monad-out.hs
@@ -5,14 +5,14 @@
   [ ( x,
       y
     )
-    | x <- xs,
-      y <- ys,
-      then group by
-        -- First comment
-        ( x
-            + y
-        )
-      using
-        -- Second comment
-        groupWith -- Third comment
+  | x <- xs,
+    y <- ys,
+    then group by
+      -- First comment
+      ( x
+          + y
+      )
+    using
+      -- Second comment
+      groupWith -- Third comment
   ]
diff --git a/data/examples/declaration/value/function/comprehension/transform-multi-line1-out.hs b/data/examples/declaration/value/function/comprehension/transform-multi-line1-out.hs
--- a/data/examples/declaration/value/function/comprehension/transform-multi-line1-out.hs
+++ b/data/examples/declaration/value/function/comprehension/transform-multi-line1-out.hs
@@ -4,9 +4,9 @@
   [ ( x,
       y
     )
-    | x <- xs,
-      y <- ys,
-      then
-        -- First comment
-        reverse -- Second comment
+  | x <- xs,
+    y <- ys,
+    then
+      -- First comment
+      reverse -- Second comment
   ]
diff --git a/data/examples/declaration/value/function/comprehension/transform-multi-line2-out.hs b/data/examples/declaration/value/function/comprehension/transform-multi-line2-out.hs
--- a/data/examples/declaration/value/function/comprehension/transform-multi-line2-out.hs
+++ b/data/examples/declaration/value/function/comprehension/transform-multi-line2-out.hs
@@ -4,13 +4,13 @@
   [ ( x,
       y
     )
-    | x <- xs,
-      y <- ys,
-      then
-        -- First comment
-        sortWith
-      by
-        ( x
-            + y -- Second comment
-        )
+  | x <- xs,
+    y <- ys,
+    then
+      -- First comment
+      sortWith
+    by
+      ( x
+          + y -- Second comment
+      )
   ]
diff --git a/data/examples/declaration/value/function/comprehension/transform-multi-line3-out.hs b/data/examples/declaration/value/function/comprehension/transform-multi-line3-out.hs
--- a/data/examples/declaration/value/function/comprehension/transform-multi-line3-out.hs
+++ b/data/examples/declaration/value/function/comprehension/transform-multi-line3-out.hs
@@ -4,9 +4,9 @@
   [ ( x,
       y
     )
-    | x <- xs,
-      y <- ys,
-      then group using
-        -- First comment
-        permutations -- Second comment
+  | x <- xs,
+    y <- ys,
+    then group using
+      -- First comment
+      permutations -- Second comment
   ]
diff --git a/data/examples/declaration/value/function/comprehension/transform-multi-line4-out.hs b/data/examples/declaration/value/function/comprehension/transform-multi-line4-out.hs
--- a/data/examples/declaration/value/function/comprehension/transform-multi-line4-out.hs
+++ b/data/examples/declaration/value/function/comprehension/transform-multi-line4-out.hs
@@ -4,14 +4,14 @@
   [ ( x,
       y
     )
-    | x <- xs,
-      y <- ys,
-      then group by
-        -- First comment
-        ( x
-            + y
-        )
-      using
-        -- Second comment
-        groupWith -- Third comment
+  | x <- xs,
+    y <- ys,
+    then group by
+      -- First comment
+      ( x
+          + y
+      )
+    using
+      -- Second comment
+      groupWith -- Third comment
   ]
diff --git a/data/examples/declaration/value/function/do-multiline-with-case-out.hs b/data/examples/declaration/value/function/do-multiline-with-case-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-multiline-with-case-out.hs
@@ -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
diff --git a/data/examples/declaration/value/function/do-multiline-with-case.hs b/data/examples/declaration/value/function/do-multiline-with-case.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-multiline-with-case.hs
@@ -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
diff --git a/data/examples/declaration/value/function/do-single-line-case-guards-out.hs b/data/examples/declaration/value/function/do-single-line-case-guards-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-case-guards-out.hs
@@ -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" }
diff --git a/data/examples/declaration/value/function/do-single-line-case-guards.hs b/data/examples/declaration/value/function/do-single-line-case-guards.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-case-guards.hs
@@ -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"}}
diff --git a/data/examples/declaration/value/function/do-single-line-lambda-case-out.hs b/data/examples/declaration/value/function/do-single-line-lambda-case-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-lambda-case-out.hs
@@ -0,0 +1,2 @@
+processValue :: Maybe Int -> IO ()
+processValue x = do putStrLn "Processing:"; \case { Just n -> print n; Nothing -> putStrLn "Empty" } x; putStrLn "Done"
diff --git a/data/examples/declaration/value/function/do-single-line-lambda-case.hs b/data/examples/declaration/value/function/do-single-line-lambda-case.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-lambda-case.hs
@@ -0,0 +1,2 @@
+processValue :: Maybe Int -> IO ()
+processValue x = do {putStrLn "Processing:"; \case {Just n -> print n; Nothing -> putStrLn "Empty"} x; putStrLn "Done"}
diff --git a/data/examples/declaration/value/function/do-single-line-multiple-cases-out.hs b/data/examples/declaration/value/function/do-single-line-multiple-cases-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-multiple-cases-out.hs
@@ -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" }
diff --git a/data/examples/declaration/value/function/do-single-line-multiple-cases.hs b/data/examples/declaration/value/function/do-single-line-multiple-cases.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-multiple-cases.hs
@@ -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"}}
diff --git a/data/examples/declaration/value/function/do-single-line-nested-case-out.hs b/data/examples/declaration/value/function/do-single-line-nested-case-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-nested-case-out.hs
@@ -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"
diff --git a/data/examples/declaration/value/function/do-single-line-nested-case.hs b/data/examples/declaration/value/function/do-single-line-nested-case.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-nested-case.hs
@@ -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"}
diff --git a/data/examples/declaration/value/function/do-single-line-with-case-out.hs b/data/examples/declaration/value/function/do-single-line-with-case-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-with-case-out.hs
@@ -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!" }
diff --git a/data/examples/declaration/value/function/do-single-line-with-case.hs b/data/examples/declaration/value/function/do-single-line-with-case.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do-single-line-with-case.hs
@@ -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!"}}
diff --git a/data/examples/declaration/value/function/do/do-dollar-mixed-chain-out.hs b/data/examples/declaration/value/function/do/do-dollar-mixed-chain-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do/do-dollar-mixed-chain-out.hs
@@ -0,0 +1,23 @@
+module A (a) where
+
+a :: Int
+a = do
+  f
+    . do
+      g
+    $ do
+      1
+        + 2
+          * 3
+    $ ( 6
+          * 5
+          + 4
+      )
+
+b =
+  f a <> 1 + 2 <> do
+    3
+
+c =
+  f b $ 1 + 2 $ do
+    3
diff --git a/data/examples/declaration/value/function/do/do-dollar-mixed-chain.hs b/data/examples/declaration/value/function/do/do-dollar-mixed-chain.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/do/do-dollar-mixed-chain.hs
@@ -0,0 +1,23 @@
+module A (a) where
+
+a :: Int
+a = do
+    f
+     . do
+      g
+     $ do
+      1
+       + 2
+       * 3
+     $ ( 6 *
+         5 +
+         4
+       )
+
+b =
+  f a <> 1 + 2 <> do
+    3
+
+c =
+  f b $ 1 + 2 $ do
+    3
diff --git a/data/examples/declaration/value/function/fancy-forall-0-out.hs b/data/examples/declaration/value/function/fancy-forall-0-out.hs
--- a/data/examples/declaration/value/function/fancy-forall-0-out.hs
+++ b/data/examples/declaration/value/function/fancy-forall-0-out.hs
@@ -2,9 +2,8 @@
   forall outertag innertag t outer inner m a.
   ( forall x. Coercible (t m x) (m x),
     forall m'.
-    HasCatch outertag outer m' =>
+    (HasCatch outertag outer m') =>
     HasCatch innertag inner (t m'),
     HasCatch outertag outer m
   ) =>
-  (forall m'. HasCatch innertag inner m' => m' a) ->
-  m a
+  (forall m'. (HasCatch innertag inner m') => m' a) -> m a
diff --git a/data/examples/declaration/value/function/fancy-forall-1-out.hs b/data/examples/declaration/value/function/fancy-forall-1-out.hs
--- a/data/examples/declaration/value/function/fancy-forall-1-out.hs
+++ b/data/examples/declaration/value/function/fancy-forall-1-out.hs
@@ -2,9 +2,8 @@
   forall outertag innertag t outer inner m a.
   ( forall x. Coercible (t m x) (m x),
     forall m'.
-    HasReader outertag outer m' =>
+    (HasReader outertag outer m') =>
     HasReader innertag inner (t m'),
     HasReader outertag outer m
   ) =>
-  (forall m'. HasReader innertag inner m' => m' a) ->
-  m a
+  (forall m'. (HasReader innertag inner m') => m' a) -> m a
diff --git a/data/examples/declaration/value/function/guards-out.hs b/data/examples/declaration/value/function/guards-out.hs
--- a/data/examples/declaration/value/function/guards-out.hs
+++ b/data/examples/declaration/value/function/guards-out.hs
@@ -10,3 +10,5 @@
 quux :: Int -> Int
 quux x | x < 0 = x
 quux x = x
+
+(a, b) | c = d
diff --git a/data/examples/declaration/value/function/guards.hs b/data/examples/declaration/value/function/guards.hs
--- a/data/examples/declaration/value/function/guards.hs
+++ b/data/examples/declaration/value/function/guards.hs
@@ -10,3 +10,5 @@
 quux :: Int -> Int
 quux x | x < 0 = x
 quux x = x
+
+(a, b) | c = d
diff --git a/data/examples/declaration/value/function/if-single-line-functions-do-out.hs b/data/examples/declaration/value/function/if-single-line-functions-do-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-single-line-functions-do-out.hs
@@ -0,0 +1,5 @@
+foo x = do
+  y <- quux
+  if x > 2
+    then bar x
+    else baz y
diff --git a/data/examples/declaration/value/function/if-single-line-functions-do.hs b/data/examples/declaration/value/function/if-single-line-functions-do.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-single-line-functions-do.hs
@@ -0,0 +1,5 @@
+foo x = do
+  y <- quux
+  if x > 2
+    then bar x
+    else baz y
diff --git a/data/examples/declaration/value/function/if-single-line-functions-out.hs b/data/examples/declaration/value/function/if-single-line-functions-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-single-line-functions-out.hs
@@ -0,0 +1,4 @@
+foo x =
+  if x > 2
+    then bar x
+    else baz x
diff --git a/data/examples/declaration/value/function/if-single-line-functions.hs b/data/examples/declaration/value/function/if-single-line-functions.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-single-line-functions.hs
@@ -0,0 +1,4 @@
+foo x =
+  if x > 2
+    then bar x
+    else baz x
diff --git a/data/examples/declaration/value/function/if-with-comment-above-branches-out.hs b/data/examples/declaration/value/function/if-with-comment-above-branches-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-above-branches-out.hs
@@ -0,0 +1,7 @@
+foo =
+  if undefined
+    -- then comment
+    then undefined
+    -- else comment
+    else do
+      undefined
diff --git a/data/examples/declaration/value/function/if-with-comment-above-branches.hs b/data/examples/declaration/value/function/if-with-comment-above-branches.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-above-branches.hs
@@ -0,0 +1,8 @@
+foo =
+  if undefined
+    -- then comment
+    then undefined
+    -- else comment
+    else
+      do
+        undefined
diff --git a/data/examples/declaration/value/function/if-with-comment-before-do-blocks-out.hs b/data/examples/declaration/value/function/if-with-comment-before-do-blocks-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-before-do-blocks-out.hs
@@ -0,0 +1,10 @@
+foo =
+  if something
+    -- then comment
+    then do
+      stuff
+      stuff
+    -- else comment
+    else do
+      stuff
+      stuff
diff --git a/data/examples/declaration/value/function/if-with-comment-before-do-blocks.hs b/data/examples/declaration/value/function/if-with-comment-before-do-blocks.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-before-do-blocks.hs
@@ -0,0 +1,10 @@
+foo =
+  if something
+    -- then comment
+    then do
+      stuff
+      stuff
+    -- else comment
+    else do
+      stuff
+      stuff
diff --git a/data/examples/declaration/value/function/if-with-comment-in-branches-out.hs b/data/examples/declaration/value/function/if-with-comment-in-branches-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-in-branches-out.hs
@@ -0,0 +1,14 @@
+foo =
+  if x
+    then
+      -- comment 1
+      -- comment 2
+      case a of
+        Just b -> _
+        Nothing -> _
+    else
+      -- comment 3
+      -- comment 4
+      case a of
+        Just b -> _
+        Nothing -> _
diff --git a/data/examples/declaration/value/function/if-with-comment-in-branches-with-functions-out.hs b/data/examples/declaration/value/function/if-with-comment-in-branches-with-functions-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-in-branches-with-functions-out.hs
@@ -0,0 +1,12 @@
+foo =
+  if x
+    then
+      -- comment 1
+      -- comment 2
+      foo 1 2 3
+    else
+      -- comment 3
+      -- comment 4
+      foo
+        "here"
+        "there"
diff --git a/data/examples/declaration/value/function/if-with-comment-in-branches-with-functions.hs b/data/examples/declaration/value/function/if-with-comment-in-branches-with-functions.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-in-branches-with-functions.hs
@@ -0,0 +1,12 @@
+foo =
+  if x
+    then
+      -- comment 1
+      -- comment 2
+      foo 1 2 3
+    else
+      -- comment 3
+      -- comment 4
+      foo
+        "here"
+        "there"
diff --git a/data/examples/declaration/value/function/if-with-comment-in-branches.hs b/data/examples/declaration/value/function/if-with-comment-in-branches.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-in-branches.hs
@@ -0,0 +1,14 @@
+foo =
+  if x
+    then
+      -- comment 1
+      -- comment 2
+      case a of
+        Just b -> _
+        Nothing -> _
+    else
+      -- comment 3
+      -- comment 4
+      case a of
+        Just b -> _
+        Nothing -> _
diff --git a/data/examples/declaration/value/function/if-with-comment-next-to-keyword-out.hs b/data/examples/declaration/value/function/if-with-comment-next-to-keyword-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-next-to-keyword-out.hs
@@ -0,0 +1,10 @@
+foo =
+  if x
+    then -- comment 1
+    -- comment 2
+      foo 1 2 3
+    else -- comment 3
+    -- comment 4
+      foo
+        "here"
+        "there"
diff --git a/data/examples/declaration/value/function/if-with-comment-next-to-keyword.hs b/data/examples/declaration/value/function/if-with-comment-next-to-keyword.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/if-with-comment-next-to-keyword.hs
@@ -0,0 +1,10 @@
+foo =
+  if x
+    then -- comment 1
+      -- comment 2
+      foo 1 2 3
+    else -- comment 3
+      -- comment 4
+      foo
+        "here"
+        "there"
diff --git a/data/examples/declaration/value/function/if-with-comment-out.hs b/data/examples/declaration/value/function/if-with-comment-out.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/if-with-comment-out.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-foo =
-  if undefined
-    then -- then comment
-      undefined
-    else -- else comment
-
-    do
-      undefined
diff --git a/data/examples/declaration/value/function/if-with-comment.hs b/data/examples/declaration/value/function/if-with-comment.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/if-with-comment.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-foo =
-  if undefined
-    -- then comment
-    then undefined
-    -- else comment
-    else
-      do
-        undefined
diff --git a/data/examples/declaration/value/function/implicit-params-out.hs b/data/examples/declaration/value/function/implicit-params-out.hs
--- a/data/examples/declaration/value/function/implicit-params-out.hs
+++ b/data/examples/declaration/value/function/implicit-params-out.hs
@@ -11,6 +11,5 @@
       a -> a -> Bool,
     ?foo :: Int
   ) =>
-  [a] ->
-  [a]
+  [a] -> [a]
 sort' = sort
diff --git a/data/examples/declaration/value/function/infix/applicative-out.hs b/data/examples/declaration/value/function/infix/applicative-out.hs
--- a/data/examples/declaration/value/function/infix/applicative-out.hs
+++ b/data/examples/declaration/value/function/infix/applicative-out.hs
@@ -1,4 +1,5 @@
 f =
-  Foo <$> bar
+  Foo
+    <$> bar
     <*> baz
     <*> baz'
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-1-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-1-out.hs
@@ -0,0 +1,22 @@
+module Main where
+
+foo =
+  fmap escapeLeadingDollar
+    . dropPaddingSpace
+    . dropWhileEnd T.null
+    . fmap (T.stripEnd . T.pack)
+    . lines
+    $ unpackHDS docStr
+
+foo =
+  when (GHC.xopt Cpp dynFlags && not cfgTolerateCpp) $
+    throwIO (OrmoluCppEnabled path)
+
+foo =
+  bar $
+    baz $
+      quux
+
+x =
+  case l of { A -> B } $
+    case q of r -> s
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-1.hs b/data/examples/declaration/value/function/infix/dollar-chains-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-1.hs
@@ -0,0 +1,22 @@
+module Main where
+
+foo =
+  fmap escapeLeadingDollar
+    . dropPaddingSpace
+    . dropWhileEnd T.null
+    . fmap (T.stripEnd . T.pack)
+    . lines
+    $ unpackHDS docStr
+
+foo =
+  when (GHC.xopt Cpp dynFlags && not cfgTolerateCpp) $
+    throwIO (OrmoluCppEnabled path)
+
+foo =
+  bar
+    $ baz
+    $ quux
+
+x =
+  case l of { A -> B } $
+  case q of r -> s
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-2-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-2-out.hs
@@ -0,0 +1,17 @@
+module Main where
+
+foo =
+  bar
+    $ do 1
+    $ quux
+
+abc =
+  a1 $
+    a2 $ do
+      3
+
+cde =
+  b1 $
+    b2 $
+      b3 $ \c ->
+        putStrLn "foo"
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-2.hs b/data/examples/declaration/value/function/infix/dollar-chains-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-2.hs
@@ -0,0 +1,18 @@
+module Main where
+
+foo =
+  bar $
+    do 1
+    $
+      quux
+
+abc =
+  a1 $
+    a2 $ do
+      3
+
+cde =
+  b1
+    $ b2
+    $ b3 $ \c ->
+      putStrLn "foo"
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-3-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-3-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-3-out.hs
@@ -0,0 +1,11 @@
+ex1 =
+  f1 $
+    arg1 $
+      arg2 $
+        arg3
+
+ex3 =
+  f1 $
+    arg1 $
+      arg2 $
+        1 + 3
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-3.hs b/data/examples/declaration/value/function/infix/dollar-chains-3.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-3.hs
@@ -0,0 +1,9 @@
+ex1 =
+  f1 $
+    arg1 $
+      arg2 $ arg3
+
+ex3 =
+  f1 $
+    arg1 $
+      arg2 $ 1 + 3
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-4-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-4-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-4-out.hs
@@ -0,0 +1,5 @@
+ex2 =
+  f1 $
+    arg1 $
+      arg2 $
+        f2 arg3
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-4.hs b/data/examples/declaration/value/function/infix/dollar-chains-4.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-4.hs
@@ -0,0 +1,4 @@
+ex2 =
+  f1 $
+    arg1 $
+      arg2 $ f2 arg3
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-5-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-5-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-5-out.hs
@@ -0,0 +1,11 @@
+ex4 =
+  f1 $
+    arg1 $
+      arg2 $ do
+        arg3
+
+ex6 =
+  f1 $
+    arg1 $
+      arg2 $
+        do arg3
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-5.hs b/data/examples/declaration/value/function/infix/dollar-chains-5.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-5.hs
@@ -0,0 +1,10 @@
+ex4 =
+  f1 $
+    arg1 $
+      arg2 $ do
+        arg3
+
+ex6 =
+  f1 $
+    arg1 $
+      arg2 $ do arg3
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-6-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-6-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-6-out.hs
@@ -0,0 +1,11 @@
+ex5 =
+  f1 $
+    arg1 $
+      arg2 $ \c ->
+        3 * c
+
+ex7 =
+  f1 $
+    arg1 $
+      arg2 $
+        \c -> 3 * c
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-6.hs b/data/examples/declaration/value/function/infix/dollar-chains-6.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/dollar-chains-6.hs
@@ -0,0 +1,10 @@
+ex5 =
+  f1 $
+    arg1 $
+      arg2 $ \c ->
+        3 * c
+
+ex7 =
+  f1 $
+    arg1 $
+      arg2 $ \c -> 3 * c
diff --git a/data/examples/declaration/value/function/infix/dollar-chains-out.hs b/data/examples/declaration/value/function/infix/dollar-chains-out.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/infix/dollar-chains-out.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Main where
-
-foo =
-  fmap escapeLeadingDollar
-    . dropPaddingSpace
-    . dropWhileEnd T.null
-    . fmap (T.stripEnd . T.pack)
-    . lines
-    $ unpackHDS docStr
-
-foo =
-  when (GHC.xopt Cpp dynFlags && not cfgTolerateCpp) $
-    throwIO (OrmoluCppEnabled path)
-
-foo =
-  bar $
-    baz $
-      quux
-
-x =
-  case l of { A -> B } $
-    case q of r -> s
diff --git a/data/examples/declaration/value/function/infix/dollar-chains.hs b/data/examples/declaration/value/function/infix/dollar-chains.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/infix/dollar-chains.hs
+++ /dev/null
@@ -1,22 +0,0 @@
-module Main where
-
-foo =
-  fmap escapeLeadingDollar
-    . dropPaddingSpace
-    . dropWhileEnd T.null
-    . fmap (T.stripEnd . T.pack)
-    . lines
-    $ unpackHDS docStr
-
-foo =
-  when (GHC.xopt Cpp dynFlags && not cfgTolerateCpp) $
-    throwIO (OrmoluCppEnabled path)
-
-foo =
-  bar
-    $ baz
-    $ quux
-
-x =
-  case l of { A -> B } $
-  case q of r -> s
diff --git a/data/examples/declaration/value/function/infix/esqueleto-0-out.hs b/data/examples/declaration/value/function/infix/esqueleto-0-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/esqueleto-0-out.hs
@@ -0,0 +1,12 @@
+import Database.Esqueleto.Experimental
+
+foo = select $ do
+  t <-
+    from $
+      table @Bar
+        `innerJoin` table @Baz
+          `on` do
+            \(br :& bz) -> whatever
+  where_ $
+    t ^. BarInt ==. val 3
+      &&. t ^. BarName `in_` valList ["hello", "world"]
diff --git a/data/examples/declaration/value/function/infix/esqueleto-0.hs b/data/examples/declaration/value/function/infix/esqueleto-0.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/esqueleto-0.hs
@@ -0,0 +1,10 @@
+import Database.Esqueleto.Experimental
+
+foo = select $ do
+  t <- from $ table @Bar
+    `innerJoin` table @Baz
+      `on` do
+        \(br :& bz) -> whatever
+  where_ $
+    t ^. BarInt ==. val 3
+   &&. t ^. BarName `in_` valList ["hello", "world"]
diff --git a/data/examples/declaration/value/function/infix/esqueleto-1-out.hs b/data/examples/declaration/value/function/infix/esqueleto-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/esqueleto-1-out.hs
@@ -0,0 +1,9 @@
+import qualified Database.Esqueleto.Experimental as E
+
+foo =
+  E.from $
+    E.table
+      `E.innerJoin` E.table
+        `E.on` ( \(a :& b) ->
+                   a E.^. AField E.==. b E.^. BField
+               )
diff --git a/data/examples/declaration/value/function/infix/esqueleto-1.hs b/data/examples/declaration/value/function/infix/esqueleto-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/esqueleto-1.hs
@@ -0,0 +1,9 @@
+import qualified Database.Esqueleto.Experimental as E
+
+foo =
+  E.from $
+    E.table
+      `E.innerJoin` E.table
+      `E.on` ( \(a :& b) ->
+               a E.^. AField E.==. b E.^. BField
+             )
diff --git a/data/examples/declaration/value/function/infix/fractional-precedence-out.hs b/data/examples/declaration/value/function/infix/fractional-precedence-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/fractional-precedence-out.hs
@@ -0,0 +1,3 @@
+startFormTok |~| messageTag
+  >~< startMessageTok |~| name
+  >~< p' |~| endMessageTok |~| endFormTok
diff --git a/data/examples/declaration/value/function/infix/fractional-precedence.hs b/data/examples/declaration/value/function/infix/fractional-precedence.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/fractional-precedence.hs
@@ -0,0 +1,3 @@
+startFormTok |~| messageTag
+  >~< startMessageTok |~| name
+  >~< p' |~| endMessageTok |~| endFormTok
diff --git a/data/examples/declaration/value/function/infix/hspec-expectation-out.hs b/data/examples/declaration/value/function/infix/hspec-expectation-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/hspec-expectation-out.hs
@@ -0,0 +1,8 @@
+import Test.Hspec
+
+my_tests = do
+  context "my context" $
+    it "does something" $
+      property $ \pos xs0 xs1 ->
+        FancyError pos xs0 <> FancyError pos xs1
+          `shouldBe` (FancyError pos (E.union xs0 xs1) :: PE)
diff --git a/data/examples/declaration/value/function/infix/hspec-expectation.hs b/data/examples/declaration/value/function/infix/hspec-expectation.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/hspec-expectation.hs
@@ -0,0 +1,8 @@
+import Test.Hspec
+
+my_tests = do
+  context "my context" $
+    it "does something" $
+      property $ \pos xs0 xs1 ->
+        FancyError pos xs0 <> FancyError pos xs1
+          `shouldBe` (FancyError pos (E.union xs0 xs1) :: PE)
diff --git a/data/examples/declaration/value/function/infix/lenses-out.hs b/data/examples/declaration/value/function/infix/lenses-out.hs
--- a/data/examples/declaration/value/function/infix/lenses-out.hs
+++ b/data/examples/declaration/value/function/infix/lenses-out.hs
@@ -1,3 +1,5 @@
+import Control.Lens
+
 lenses =
   Just $
     M.fromList $
@@ -15,7 +17,8 @@
 
 wreq =
   let opts =
-        defaults & auth ?~ awsAuth AWSv4 "key" "secret"
+        defaults
+          & auth ?~ awsAuth AWSv4 "key" "secret"
           & header "Accept" .~ ["application/json"]
           & header "Runscope-Bucket-Auth" .~ ["1example-1111-4yyyy-zzzz-xxxxxxxx"]
    in getWith opts
diff --git a/data/examples/declaration/value/function/infix/lenses.hs b/data/examples/declaration/value/function/infix/lenses.hs
--- a/data/examples/declaration/value/function/infix/lenses.hs
+++ b/data/examples/declaration/value/function/infix/lenses.hs
@@ -1,3 +1,5 @@
+import Control.Lens
+
 lenses = Just $ M.fromList
   $ "type"       .= ("user.connection" :: Text)
   # "connection" .= uc
diff --git a/data/examples/declaration/value/function/infix/op-chain-colon-do-out.hs b/data/examples/declaration/value/function/infix/op-chain-colon-do-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-colon-do-out.hs
@@ -0,0 +1,10 @@
+-- chain mixing : and do-blocks
+d =
+  do
+    1
+    : do
+      2
+    : do
+      3
+    : do
+      4
diff --git a/data/examples/declaration/value/function/infix/op-chain-colon-do.hs b/data/examples/declaration/value/function/infix/op-chain-colon-do.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-colon-do.hs
@@ -0,0 +1,10 @@
+-- chain mixing : and do-blocks
+d =
+  do
+    1
+  : do
+    2
+  : do
+    3
+  : do
+    4
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-eq-out.hs b/data/examples/declaration/value/function/infix/op-chain-l-eq-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-eq-out.hs
@@ -0,0 +1,11 @@
+-- Left chain, 2 operators with p(a) == p(b)
+s :: Int
+s =
+  1
+    + 2
+    + 3
+    + 4
+    - 5
+    + 6
+    + 7
+    + 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-eq.hs b/data/examples/declaration/value/function/infix/op-chain-l-eq.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-eq.hs
@@ -0,0 +1,11 @@
+-- Left chain, 2 operators with p(a) == p(b)
+s :: Int
+s =
+  1 +
+  2 +
+  3 +
+  4 -
+  5 +
+  6 +
+  7 +
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-gt-out.hs b/data/examples/declaration/value/function/infix/op-chain-l-gt-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-gt-out.hs
@@ -0,0 +1,11 @@
+-- Left chain, 2 operators with p(a) > p(b)
+t :: Int
+t =
+  1
+    + 2
+    + 3
+    + 4
+    >> 5
+      + 6
+      + 7
+      + 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-gt.hs b/data/examples/declaration/value/function/infix/op-chain-l-gt.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-gt.hs
@@ -0,0 +1,11 @@
+-- Left chain, 2 operators with p(a) > p(b)
+t :: Int
+t =
+  1 +
+  2 +
+  3 +
+  4 >>
+  5 +
+  6 +
+  7 +
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-lt-out.hs b/data/examples/declaration/value/function/infix/op-chain-l-lt-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-lt-out.hs
@@ -0,0 +1,11 @@
+-- Left chain, 2 operators with p(a) < p(b)
+u :: Int
+u =
+  1
+    + 2
+    + 3
+    + 4
+      * 5
+    + 6
+    + 7
+    + 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-lt.hs b/data/examples/declaration/value/function/infix/op-chain-l-lt.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-lt.hs
@@ -0,0 +1,11 @@
+-- Left chain, 2 operators with p(a) < p(b)
+u :: Int
+u =
+  1 +
+  2 +
+  3 +
+  4 *
+  5 +
+  6 +
+  7 +
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-s-out.hs b/data/examples/declaration/value/function/infix/op-chain-l-s-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-s-out.hs
@@ -0,0 +1,7 @@
+-- Left chain, 1 operator type
+r :: Int
+r =
+  1
+    + 2
+    + 3
+    + 4
diff --git a/data/examples/declaration/value/function/infix/op-chain-l-s.hs b/data/examples/declaration/value/function/infix/op-chain-l-s.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-l-s.hs
@@ -0,0 +1,7 @@
+-- Left chain, 1 operator type
+r :: Int
+r =
+  1 +
+  2 +
+  3 +
+  4
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-1-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-1-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(:) == p(b)
+h :: Int
+h =
+  1
+    : 2
+    : 3
+    : 4
+    ++ 5
+    : 6
+    : 7
+    : 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-1.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-1.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(:) == p(b)
+h :: Int
+h =
+  1 :
+  2 :
+  3 :
+  4 ++
+  5 :
+  6 :
+  7 :
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-2-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-2-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(a) == p(:)
+k :: Int
+k =
+  1
+    ++ 2
+    ++ 3
+    ++ 4
+    : 5
+    ++ 6
+    ++ 7
+    ++ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-2.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-colon-2.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(a) == p(:)
+k :: Int
+k =
+  1 ++
+  2 ++
+  3 ++
+  4 :
+  5 ++
+  6 ++
+  7 ++
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, $ case, 2 operators with p($) == p(b)
+n :: Int
+n =
+  1 $
+    2 $
+      3 $
+        4 `seq`
+          5 $
+            6 $
+              7 $
+                8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-1.hs
@@ -0,0 +1,11 @@
+-- Right chain, $ case, 2 operators with p($) == p(b)
+n :: Int
+n =
+  1 $
+  2 $
+  3 $
+  4 `seq`
+  5 $
+  6 $
+  7 $
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2-out.hs
@@ -0,0 +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
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-dollar-2.hs
@@ -0,0 +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
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-normal-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-normal-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-normal-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, normal case, 2 operators with p(a) == p(b)
+e :: Int
+e =
+  1
+    ^^ 2
+    ^^ 3
+    ^^ 4
+    ^ 5
+    ^^ 6
+    ^^ 7
+    ^^ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-eq-normal.hs b/data/examples/declaration/value/function/infix/op-chain-r-eq-normal.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-eq-normal.hs
@@ -0,0 +1,11 @@
+-- Right chain, normal case, 2 operators with p(a) == p(b)
+e :: Int
+e =
+  1 ^^
+  2 ^^
+  3 ^^
+  4 ^
+  5 ^^
+  6 ^^
+  7 ^^
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-1-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-1-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(:) > p(b)
+i :: Int
+i =
+  1
+    : 2
+    : 3
+    : 4
+    `seq` 5
+      : 6
+      : 7
+      : 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-1.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-1.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(:) > p(b)
+i :: Int
+i =
+  1 :
+  2 :
+  3 :
+  4 `seq`
+  5 :
+  6 :
+  7 :
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-2-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-2-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(a) > p(:)
+l :: Int
+l =
+  1
+    ^^ 2
+    ^^ 3
+    ^^ 4
+    : 5
+      ^^ 6
+      ^^ 7
+      ^^ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-2.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-colon-2.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(a) > p(:)
+l :: Int
+l =
+  1 ^^
+  2 ^^
+  3 ^^
+  4 :
+  5 ^^
+  6 ^^
+  7 ^^
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-dollar-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-dollar-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-dollar-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, $ case, 2 operators with p(a) > p($)
+q :: Int
+q =
+  1
+    ++ 2
+    ++ 3
+    ++ 4
+    $ 5
+      ++ 6
+      ++ 7
+      ++ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-dollar.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-dollar.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-dollar.hs
@@ -0,0 +1,11 @@
+-- Right chain, $ case, 2 operators with p(a) > p($)
+q :: Int
+q =
+  1 ++
+  2 ++
+  3 ++
+  4 $
+  5 ++
+  6 ++
+  7 ++
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-normal-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-normal-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-normal-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, normal case, 2 operators with p(a) > p(b)
+f :: Int
+f =
+  1
+    ^^ 2
+    ^^ 3
+    ^^ 4
+    ++ 5
+      ^^ 6
+      ^^ 7
+      ^^ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-gt-normal.hs b/data/examples/declaration/value/function/infix/op-chain-r-gt-normal.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-gt-normal.hs
@@ -0,0 +1,11 @@
+-- Right chain, normal case, 2 operators with p(a) > p(b)
+f :: Int
+f =
+  1 ^^
+  2 ^^
+  3 ^^
+  4 ++
+  5 ^^
+  6 ^^
+  7 ^^
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-1-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-1-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(:) < p(b)
+j :: Int
+j =
+  1
+    : 2
+    : 3
+    : 4
+      ^^ 5
+    : 6
+    : 7
+    : 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-1.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-1.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(:) < p(b)
+j :: Int
+j =
+  1 :
+  2 :
+  3 :
+  4 ^^
+  5 :
+  6 :
+  7 :
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-2-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-2-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(a) < p(:)
+m :: Int
+m =
+  1
+    || 2
+    || 3
+    || 4
+      : 5
+    || 6
+    || 7
+    || 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-2.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-colon-2.hs
@@ -0,0 +1,11 @@
+-- Right chain, : case, 2 operators with p(a) < p(:)
+m :: Int
+m =
+  1 ||
+  2 ||
+  3 ||
+  4 :
+  5 ||
+  6 ||
+  7 ||
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-dollar-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-dollar-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-dollar-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, $ case, 2 operators with p($) < p(b)
+o :: Int
+o =
+  1
+    $ 2
+    $ 3
+    $ 4
+      ++ 5
+    $ 6
+    $ 7
+    $ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-dollar.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-dollar.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-dollar.hs
@@ -0,0 +1,11 @@
+-- Right chain, $ case, 2 operators with p($) < p(b)
+o :: Int
+o =
+  1 $
+  2 $
+  3 $
+  4 ++
+  5 $
+  6 $
+  7 $
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-normal-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-normal-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-normal-out.hs
@@ -0,0 +1,11 @@
+-- Right chain, normal case, 2 operators with p(a) < p(b)
+g :: Int
+g =
+  1
+    ++ 2
+    ++ 3
+    ++ 4
+      ^^ 5
+    ++ 6
+    ++ 7
+    ++ 8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-lt-normal.hs b/data/examples/declaration/value/function/infix/op-chain-r-lt-normal.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-lt-normal.hs
@@ -0,0 +1,11 @@
+-- Right chain, normal case, 2 operators with p(a) < p(b)
+g :: Int
+g =
+  1 ++
+  2 ++
+  3 ++
+  4 ^^
+  5 ++
+  6 ++
+  7 ++
+  8
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-s-colon-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-s-colon-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-s-colon-out.hs
@@ -0,0 +1,7 @@
+-- Right chain, : case, 1 operator type
+b :: Int
+b =
+  1
+    : 2
+    : 3
+    : 4
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-s-colon.hs b/data/examples/declaration/value/function/infix/op-chain-r-s-colon.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-s-colon.hs
@@ -0,0 +1,7 @@
+-- Right chain, : case, 1 operator type
+b :: Int
+b =
+  1 :
+  2 :
+  3 :
+  4
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-s-dollar-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-s-dollar-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-s-dollar-out.hs
@@ -0,0 +1,7 @@
+-- Right chain, $ case, 1 operator type
+c :: Int
+c =
+  1 $
+    2 $
+      3 $
+        4
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-s-dollar.hs b/data/examples/declaration/value/function/infix/op-chain-r-s-dollar.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-s-dollar.hs
@@ -0,0 +1,7 @@
+-- Right chain, $ case, 1 operator type
+c :: Int
+c =
+  1 $
+  2 $
+  3 $
+  4
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-s-normal-out.hs b/data/examples/declaration/value/function/infix/op-chain-r-s-normal-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-s-normal-out.hs
@@ -0,0 +1,7 @@
+-- Right chain, normal case, 1 operator type
+a :: Int
+a =
+  1
+    ++ 2
+    ++ 3
+    ++ 4
diff --git a/data/examples/declaration/value/function/infix/op-chain-r-s-normal.hs b/data/examples/declaration/value/function/infix/op-chain-r-s-normal.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/op-chain-r-s-normal.hs
@@ -0,0 +1,7 @@
+-- Right chain, normal case, 1 operator type
+a :: Int
+a =
+  1 ++
+  2 ++
+  3 ++
+  4
diff --git a/data/examples/declaration/value/function/infix/qualified-ops-out.hs b/data/examples/declaration/value/function/infix/qualified-ops-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/qualified-ops-out.hs
@@ -0,0 +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.# []
diff --git a/data/examples/declaration/value/function/infix/qualified-ops.hs b/data/examples/declaration/value/function/infix/qualified-ops.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/qualified-ops.hs
@@ -0,0 +1,7 @@
+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.# []
diff --git a/data/examples/declaration/value/function/infix/qualified-prelude-out.hs b/data/examples/declaration/value/function/infix/qualified-prelude-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/qualified-prelude-out.hs
@@ -0,0 +1,12 @@
+module StreamSpec where
+
+import Prelude (($))
+import qualified Prelude
+
+spec :: Spec
+spec = do
+  describe "Comparing list function to" $ do
+    qit "yieldMany" $
+      \(mono :: Seq Int) ->
+        yieldMany mono
+          `checkProducer` otoList mono
diff --git a/data/examples/declaration/value/function/infix/qualified-prelude.hs b/data/examples/declaration/value/function/infix/qualified-prelude.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/qualified-prelude.hs
@@ -0,0 +1,12 @@
+module StreamSpec where
+
+import qualified Prelude
+import           Prelude (($))
+
+spec :: Spec
+spec = do
+    describe "Comparing list function to" $ do
+        qit "yieldMany" $
+            \(mono :: Seq Int) ->
+                yieldMany mono `checkProducer`
+                otoList mono
diff --git a/data/examples/declaration/value/function/infix/tricky-colon-chain-out.hs b/data/examples/declaration/value/function/infix/tricky-colon-chain-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/tricky-colon-chain-out.hs
@@ -0,0 +1,9 @@
+b :: [Int]
+b =
+  1
+    : do
+      2
+    : do
+      3
+    : do
+      [4]
diff --git a/data/examples/declaration/value/function/infix/tricky-colon-chain.hs b/data/examples/declaration/value/function/infix/tricky-colon-chain.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/infix/tricky-colon-chain.hs
@@ -0,0 +1,9 @@
+b :: [Int]
+b =
+  1 :
+    do
+      2
+     : do
+        3
+       : do
+          [4]
diff --git a/data/examples/declaration/value/function/lambda-case-out.hs b/data/examples/declaration/value/function/lambda-case-out.hs
--- a/data/examples/declaration/value/function/lambda-case-out.hs
+++ b/data/examples/declaration/value/function/lambda-case-out.hs
@@ -7,3 +7,17 @@
   5 -> 10
   i | i > 5 -> 11
   _ -> 12
+
+foo :: Maybe a -> Maybe a -> Maybe a -> Int
+foo = \cases Nothing Just {} Nothing -> 1; _ _ _ -> 0
+
+foo :: Maybe Int -> Maybe Int -> Int
+foo = \cases
+  (Just a) (Just a) -> a + a
+  _ _ -> 0
+
+foo :: Bool -> Bool -> Bool
+foo = \cases
+  True
+    True -> True
+  _ _ -> False
diff --git a/data/examples/declaration/value/function/lambda-case.hs b/data/examples/declaration/value/function/lambda-case.hs
--- a/data/examples/declaration/value/function/lambda-case.hs
+++ b/data/examples/declaration/value/function/lambda-case.hs
@@ -7,3 +7,17 @@
   5 -> 10
   i  | i > 5 -> 11
   _ -> 12
+
+foo :: Maybe a -> Maybe a -> Maybe a -> Int
+foo = \cases Nothing Just{} Nothing -> 1;  _ _ _ -> 0
+
+foo :: Maybe Int -> Maybe Int -> Int
+foo = \cases
+  (Just a) (Just a) -> a + a
+  _         _       -> 0
+
+foo :: Bool -> Bool -> Bool
+foo = \cases
+  True
+   True -> True
+  _ _ -> False
diff --git a/data/examples/declaration/value/function/let-multi-line-out.hs b/data/examples/declaration/value/function/let-multi-line-out.hs
--- a/data/examples/declaration/value/function/let-multi-line-out.hs
+++ b/data/examples/declaration/value/function/let-multi-line-out.hs
@@ -17,7 +17,7 @@
                    () -> undefined
    in go
 
-implicitParams :: HasCallStack => Int
+implicitParams :: (HasCallStack) => Int
 implicitParams =
   let ?cs = ?callstack
    in foo cs
diff --git a/data/examples/declaration/value/function/linear-bindings-out.hs b/data/examples/declaration/value/function/linear-bindings-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/linear-bindings-out.hs
@@ -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 ()
diff --git a/data/examples/declaration/value/function/linear-bindings.hs b/data/examples/declaration/value/function/linear-bindings.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/linear-bindings.hs
@@ -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 ()
diff --git a/data/examples/declaration/value/function/list-comprehensions-out.hs b/data/examples/declaration/value/function/list-comprehensions-out.hs
--- a/data/examples/declaration/value/function/list-comprehensions-out.hs
+++ b/data/examples/declaration/value/function/list-comprehensions-out.hs
@@ -4,21 +4,40 @@
 
 barbaz x y z w =
   [ (a, b, c, d) -- Foo
-    | a <-
-        x, -- Bar
-      b <- y, -- Baz
-      any even [a, b],
-      c <-
-        z
-          * z ^ 2, -- Bar baz
-      d <-
-        w
-          + w, -- Baz bar
-      all
-        even
-        [ a,
-          b,
-          c,
-          d
-        ]
+  | a <-
+      x, -- Bar
+    b <- y, -- Baz
+    any even [a, b],
+    c <-
+      z
+        * z ^ 2, -- Bar baz
+    d <-
+      w
+        + w, -- Baz bar
+    all
+      even
+      [ a,
+        b,
+        c,
+        d
+      ]
+  ]
+
+a = do
+  d <-
+    [ x + 1
+    | x <- b
+    ]
+
+  [ c
+    | c <- d
+    ]
+
+trans =
+  [ x
+  | x <- xs,
+    then
+      reverse,
+    then
+      reverse
   ]
diff --git a/data/examples/declaration/value/function/list-comprehensions.hs b/data/examples/declaration/value/function/list-comprehensions.hs
--- a/data/examples/declaration/value/function/list-comprehensions.hs
+++ b/data/examples/declaration/value/function/list-comprehensions.hs
@@ -20,3 +20,19 @@
         c, d
       ]
   ]
+
+a = do
+  d <-
+    [ x + 1
+      | x <- b
+        ]
+
+  [ c
+      | c <- d ]
+
+trans =
+  [ x
+  | x <- xs
+  , then reverse
+  , then reverse
+  ]
diff --git a/data/examples/declaration/value/function/list-notation-0-out.hs b/data/examples/declaration/value/function/list-notation-0-out.hs
--- a/data/examples/declaration/value/function/list-notation-0-out.hs
+++ b/data/examples/declaration/value/function/list-notation-0-out.hs
@@ -1,5 +1,5 @@
 foo =
-  testCase "Foo" testFoo :
-  testCase "Bar" testBar :
-  testCase "Baz" testBaz :
-  []
+  testCase "Foo" testFoo
+    : testCase "Bar" testBar
+    : testCase "Baz" testBaz
+    : []
diff --git a/data/examples/declaration/value/function/list-notation-1-out.hs b/data/examples/declaration/value/function/list-notation-1-out.hs
--- a/data/examples/declaration/value/function/list-notation-1-out.hs
+++ b/data/examples/declaration/value/function/list-notation-1-out.hs
@@ -1,8 +1,8 @@
 instance A.ToJSON UpdateTable where
   toJSON a =
     A.object $
-      "TableName" .= updateTableName a :
-      "ProvisionedThroughput" .= updateProvisionedThroughput a :
-      case updateGlobalSecondaryIndexUpdates a of
-        [] -> []
-        l -> ["GlobalSecondaryIndexUpdates" .= l]
+      "TableName" .= updateTableName a
+        : "ProvisionedThroughput" .= updateProvisionedThroughput a
+        : case updateGlobalSecondaryIndexUpdates a of
+          [] -> []
+          l -> ["GlobalSecondaryIndexUpdates" .= l]
diff --git a/data/examples/declaration/value/function/list-notation-2-out.hs b/data/examples/declaration/value/function/list-notation-2-out.hs
--- a/data/examples/declaration/value/function/list-notation-2-out.hs
+++ b/data/examples/declaration/value/function/list-notation-2-out.hs
@@ -1,6 +1,6 @@
 -- A list of the element and all its parents up to the root node.
 getPath tree t =
-  t :
-  case Map.lookup (getId t) tree of
-    Nothing -> []
-    Just parent -> getPath tree parent
+  t
+    : case Map.lookup (getId t) tree of
+      Nothing -> []
+      Just parent -> getPath tree parent
diff --git a/data/examples/declaration/value/function/list-notation-3-out.hs b/data/examples/declaration/value/function/list-notation-3-out.hs
--- a/data/examples/declaration/value/function/list-notation-3-out.hs
+++ b/data/examples/declaration/value/function/list-notation-3-out.hs
@@ -1,12 +1,10 @@
 foo =
-  reportSDoc "tc.cc" 30 $
-    sep $ do
-      (prettyTCM q <+> " before compilation") : do
-        map (prettyTCM . map unArg . clPats) cls
+  reportSDoc "tc.cc" 30 $ sep $ do
+    (prettyTCM q <+> " before compilation") : do
+      map (prettyTCM . map unArg . clPats) cls
 
 foo =
-  reportSDoc "tc.cc" 30 $
-    sep $ do
-      (prettyTCM q <+> " before compilation") :
-        do
-          map (prettyTCM . map unArg . clPats) cls
+  reportSDoc "tc.cc" 30 $ sep $ do
+    (prettyTCM q <+> " before compilation")
+      : do
+        map (prettyTCM . map unArg . clPats) cls
diff --git a/data/examples/declaration/value/function/multi-way-if-out.hs b/data/examples/declaration/value/function/multi-way-if-out.hs
--- a/data/examples/declaration/value/function/multi-way-if-out.hs
+++ b/data/examples/declaration/value/function/multi-way-if-out.hs
@@ -4,13 +4,15 @@
 
 bar x y =
   if
-      | x > y -> x
-      | x < y ->
+    | x > y -> x
+    | x < y ->
         y
-      | otherwise -> x
+    | otherwise -> x
 
 baz =
   if
       | p -> f
       | otherwise -> g
     x
+
+x y = if | foo -> False | otherwise -> True
diff --git a/data/examples/declaration/value/function/multi-way-if.hs b/data/examples/declaration/value/function/multi-way-if.hs
--- a/data/examples/declaration/value/function/multi-way-if.hs
+++ b/data/examples/declaration/value/function/multi-way-if.hs
@@ -12,3 +12,5 @@
   if | p -> f
      | otherwise -> g
     x
+
+x y = if | foo -> False | otherwise -> True
diff --git a/data/examples/declaration/value/function/multiline-strings-0-out.hs b/data/examples/declaration/value/function/multiline-strings-0-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-0-out.hs
@@ -0,0 +1,29 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """Line 1
+     Line 2
+  Line 3
+  """
+
+s_2 =
+  """\ \Line 1
+     Line 2
+  Line 3
+  """
+
+-- equivalent to
+s' = "Line 1\n   Line 2\nLine 3"
+
+-- the following are equivalent
+s = """hello world"""
+
+s' = "hello world"
+
+s =
+  """    hello
+  world
+  """
+
+-- equivalent to
+s' = "    hello\nworld"
diff --git a/data/examples/declaration/value/function/multiline-strings-0.hs b/data/examples/declaration/value/function/multiline-strings-0.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-0.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """Line 1
+     Line 2
+  Line 3
+  """
+
+s_2 =
+  """\
+ \Line 1
+     Line 2
+  Line 3
+  """
+
+-- equivalent to
+s' = "Line 1\n   Line 2\nLine 3"
+
+
+-- the following are equivalent
+s = """hello world"""
+s' = "hello world"
+
+
+s =
+  """    hello
+  world
+  """
+
+-- equivalent to
+s' = "    hello\nworld"
diff --git a/data/examples/declaration/value/function/multiline-strings-1-out.hs b/data/examples/declaration/value/function/multiline-strings-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-1-out.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """
+  a b\ \ c d e
+  f g
+  """
+
+-- equivalent to
+s' = "a b c d e\nf g"
+
+weirdGap = """\65\ \0"""
diff --git a/data/examples/declaration/value/function/multiline-strings-1.hs b/data/examples/declaration/value/function/multiline-strings-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-1.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+    """
+      a b\
+  \ c d e
+      f g
+    """
+
+-- equivalent to
+s' = "a b c d e\nf g"
+
+weirdGap = """\65\ \0"""
diff --git a/data/examples/declaration/value/function/multiline-strings-2-out.hs b/data/examples/declaration/value/function/multiline-strings-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-2-out.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """
+  a
+  b
+  c
+  """
+
+-- equivalent to
+s' = "a\nb\nc"
diff --git a/data/examples/declaration/value/function/multiline-strings-2.hs b/data/examples/declaration/value/function/multiline-strings-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-2.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+	"""
+	        a
+	 	b
+	    	c
+	"""
+
+-- equivalent to
+s' = "a\nb\nc"
diff --git a/data/examples/declaration/value/function/multiline-strings-3-out.hs b/data/examples/declaration/value/function/multiline-strings-3-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-3-out.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """
+
+  a
+  b
+  c
+  """
+
+-- equivalent to
+s' = "\na\nb\nc"
+
+s1 =
+  """    a
+  b
+  c
+  """
+
+s2 =
+  """
+  a
+  b
+  c
+  """
+
+-- In the current proposal, these are equivalent to
+-- the below. If leading newline were removed at the
+-- beginning, both would result in s1'.
+s1' = "    a\nb\nc"
+
+s2' = "a\nb\nc"
diff --git a/data/examples/declaration/value/function/multiline-strings-3.hs b/data/examples/declaration/value/function/multiline-strings-3.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-3.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """
+
+  a
+  b
+  c
+  """
+
+-- equivalent to
+s' = "\na\nb\nc"
+
+
+s1 =
+  """    a
+  b
+  c
+  """
+
+s2 =
+  """
+  a
+  b
+  c
+  """
+
+-- In the current proposal, these are equivalent to
+-- the below. If leading newline were removed at the
+-- beginning, both would result in s1'.
+s1' = "    a\nb\nc"
+s2' = "a\nb\nc"
diff --git a/data/examples/declaration/value/function/multiline-strings-4-out.hs b/data/examples/declaration/value/function/multiline-strings-4-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-4-out.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """
+  a
+  b
+
+  """
+
+-- equivalent to
+s' = "a\nb\n"
+
+s1 =
+  """
+  line 1
+  line 2
+  """
+
+s2 = "line 3"
+
+s3 =
+  """
+  line 4
+  line 5
+  """
diff --git a/data/examples/declaration/value/function/multiline-strings-4.hs b/data/examples/declaration/value/function/multiline-strings-4.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-4.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s =
+  """
+  a
+  b
+
+  """
+
+-- equivalent to
+s' = "a\nb\n"
+
+
+s1 =
+  """
+  line 1
+  line 2
+  """
+
+s2 = "line 3"
+
+s3 =
+  """
+  line 4
+  line 5
+  """
diff --git a/data/examples/declaration/value/function/multiline-strings-5-out.hs b/data/examples/declaration/value/function/multiline-strings-5-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-5-out.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s1 =
+  """
+  a
+  b
+  c
+  """
+
+s1' = "a\nb\nc"
+
+s2 =
+  """
+  \&  a
+    b
+    c
+  """
+
+s2_2 =
+  """
+  \&  a
+  \&  b
+  \&  c
+  """
+
+s2' = "  a\n  b\n  c"
diff --git a/data/examples/declaration/value/function/multiline-strings-5.hs b/data/examples/declaration/value/function/multiline-strings-5.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-5.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE MultilineStrings #-}
+
+s1 =
+  """
+    a
+    b
+    c
+  """
+
+s1' = "a\nb\nc"
+
+s2 =
+  """
+  \&  a
+    b
+    c
+  """
+
+s2_2 =
+  """
+  \&  a
+  \&  b
+  \&  c
+  """
+
+s2' = "  a\n  b\n  c"
diff --git a/data/examples/declaration/value/function/multiline-strings-6-out.hs b/data/examples/declaration/value/function/multiline-strings-6-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-6-out.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE MultilineStrings #-}
+
+x =
+  """
+  This is a literal multiline string:
+  \"\"\"
+  Hello
+    world!
+  \"""
+  """
diff --git a/data/examples/declaration/value/function/multiline-strings-6.hs b/data/examples/declaration/value/function/multiline-strings-6.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-6.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE MultilineStrings #-}
+
+x =
+  """
+  This is a literal multiline string:
+  \"\"\"
+  Hello
+    world!
+  \"""
+  """
diff --git a/data/examples/declaration/value/function/multiline-strings-7-out.hs b/data/examples/declaration/value/function/multiline-strings-7-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-7-out.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE MultilineStrings #-}
+
+printf
+  """
+  instance Aeson.FromJSON %s where
+    parseJSON =
+      Aeson.withText "%s" $ \\s ->
+        either Aeson.parseFail pure $
+          parsePrinterOptType (Text.unpack s)
+
+  instance PrinterOptsFieldType %s where
+    parsePrinterOptType s =
+      case s of
+  %s
+        _ ->
+          Left . unlines $
+            [ "unknown value: " <> show s
+            , "Valid values are: %s"
+            ]
+
+  """
+  fieldTypeName
+  fieldTypeName
+  fieldTypeName
+  ( unlines_
+      [ printf "      \"%s\" -> Right %s" val con
+      | (con, val) <- enumOptions
+      ]
+  )
+  (renderEnumOptions enumOptions)
diff --git a/data/examples/declaration/value/function/multiline-strings-7.hs b/data/examples/declaration/value/function/multiline-strings-7.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-7.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE MultilineStrings #-}
+
+printf
+  """
+  instance Aeson.FromJSON %s where
+    parseJSON =
+      Aeson.withText "%s" $ \\s ->
+        either Aeson.parseFail pure $
+          parsePrinterOptType (Text.unpack s)
+
+  instance PrinterOptsFieldType %s where
+    parsePrinterOptType s =
+      case s of
+  %s
+        _ ->
+          Left . unlines $
+            [ "unknown value: " <> show s
+            , "Valid values are: %s"
+            ]
+
+  """
+  fieldTypeName
+  fieldTypeName
+  fieldTypeName
+  ( unlines_
+      [ printf "      \"%s\" -> Right %s" val con
+      | (con, val) <- enumOptions
+      ]
+  )
+  (renderEnumOptions enumOptions)
diff --git a/data/examples/declaration/value/function/multiline-strings-8-out.hs b/data/examples/declaration/value/function/multiline-strings-8-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-8-out.hs
@@ -0,0 +1,12 @@
+{-# LANGUAGE MultilineStrings #-}
+
+type Foo =
+  """
+  yeah
+    yeah"""
+
+foo =
+  foo
+    @"""yeah
+     yeah
+     """
diff --git a/data/examples/declaration/value/function/multiline-strings-8.hs b/data/examples/declaration/value/function/multiline-strings-8.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-8.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE MultilineStrings #-}
+
+type Foo = """
+  yeah
+    yeah"""
+
+foo = foo @"""yeah
+           yeah
+           """
diff --git a/data/examples/declaration/value/function/multiline-strings-9-out.hs b/data/examples/declaration/value/function/multiline-strings-9-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-9-out.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE MultilineStrings #-}
+
+multilineBlank =
+  """
+  1
+
+
+
+
+  6
+  """
diff --git a/data/examples/declaration/value/function/multiline-strings-9.hs b/data/examples/declaration/value/function/multiline-strings-9.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-strings-9.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE MultilineStrings #-}
+
+multilineBlank =
+  """
+  1
+
+
+
+
+  6
+  """
diff --git a/data/examples/declaration/value/function/multiline-types-out.hs b/data/examples/declaration/value/function/multiline-types-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-types-out.hs
@@ -0,0 +1,17 @@
+foo ::
+  (Monad m, Show a) =>
+  a ->
+  m String
+bar ::
+  ( Monad m,
+    Show a
+  ) =>
+  a -> m String
+multiConstraints ::
+  (Show a) =>
+  a ->
+  a ->
+  a ->
+  (Eq a) =>
+  (Num a) =>
+  a -> a -> ()
diff --git a/data/examples/declaration/value/function/multiline-types.hs b/data/examples/declaration/value/function/multiline-types.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/multiline-types.hs
@@ -0,0 +1,15 @@
+foo ::
+   (Monad m, Show a) =>
+   a ->
+   m String
+
+bar ::
+   (Monad m,
+      Show a) =>
+   a -> m String
+
+multiConstraints ::
+  Show a =>
+  a -> a -> a ->
+  Eq a => Num a =>
+  a -> a -> ()
diff --git a/data/examples/declaration/value/function/multiple-guards-out.hs b/data/examples/declaration/value/function/multiple-guards-out.hs
--- a/data/examples/declaration/value/function/multiple-guards-out.hs
+++ b/data/examples/declaration/value/function/multiple-guards-out.hs
@@ -6,9 +6,9 @@
 bar :: Int -> Int
 bar x
   | x == 5 =
-    foo x
-      + foo 10
+      foo x
+        + foo 10
   | x == 6 =
-    foo x
-      + foo 20
+      foo x
+        + foo 20
   | otherwise = foo 100
diff --git a/data/examples/declaration/value/function/multiple-guards.hs b/data/examples/declaration/value/function/multiple-guards.hs
--- a/data/examples/declaration/value/function/multiple-guards.hs
+++ b/data/examples/declaration/value/function/multiple-guards.hs
@@ -1,7 +1,6 @@
 foo :: Int -> Int
-foo x
-  | x == 5 = 10
-  | otherwise = 12
+foo x | x == 5 = 10
+      | otherwise = 12
 
 bar :: Int -> Int
 bar x
diff --git a/data/examples/declaration/value/function/operator-fixity-defaulting-out.hs b/data/examples/declaration/value/function/operator-fixity-defaulting-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operator-fixity-defaulting-out.hs
@@ -0,0 +1,12 @@
+def1 =
+  (,,)
+    <$> gvar (primitiveGVar "natToHex")
+      `foo` ne
+    <*> con cNothing
+      `foo` tcon tChar
+
+def2 =
+  tds1
+    && lc1 == mempty
+    && gc1 `M.isSubmapOf` gc2
+    && sh1 == sh2
diff --git a/data/examples/declaration/value/function/operator-fixity-defaulting.hs b/data/examples/declaration/value/function/operator-fixity-defaulting.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operator-fixity-defaulting.hs
@@ -0,0 +1,12 @@
+def1 =
+  (,,)
+    <$> gvar (primitiveGVar "natToHex")
+         `foo` ne
+    <*> con cNothing
+         `foo` tcon tChar
+
+def2 =
+  tds1
+    && lc1 == mempty
+    && gc1 `M.isSubmapOf` gc2
+    && sh1 == sh2
diff --git a/data/examples/declaration/value/function/operator-hash-with-unboxed-sums-out.hs b/data/examples/declaration/value/function/operator-hash-with-unboxed-sums-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operator-hash-with-unboxed-sums-out.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE UnboxedSums #-}
+
+module Foo (( #<| )) where
+
+( #<| ) :: Int -> Int -> Int
+( #<| ) = (+)
+
+(+) = (+)
diff --git a/data/examples/declaration/value/function/operator-hash-with-unboxed-sums.hs b/data/examples/declaration/value/function/operator-hash-with-unboxed-sums.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operator-hash-with-unboxed-sums.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE UnboxedSums #-}
+
+module Foo (( #<| )) where
+
+( #<| ) :: Int -> Int -> Int
+( #<| ) = (+)
+
+(+) = (+)
diff --git a/data/examples/declaration/value/function/operator-hash-without-unboxed-sums-out.hs b/data/examples/declaration/value/function/operator-hash-without-unboxed-sums-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operator-hash-without-unboxed-sums-out.hs
@@ -0,0 +1,6 @@
+module Foo ((#<|)) where
+
+(#<|) :: Int -> Int -> Int
+(#<|) = (+)
+
+(+) = (+)
diff --git a/data/examples/declaration/value/function/operator-hash-without-unboxed-sums.hs b/data/examples/declaration/value/function/operator-hash-without-unboxed-sums.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operator-hash-without-unboxed-sums.hs
@@ -0,0 +1,6 @@
+module Foo (( #<| )) where
+
+( #<| ) :: Int -> Int -> Int
+( #<| ) = (+)
+
+(+) = (+)
diff --git a/data/examples/declaration/value/function/operators-0-out.hs b/data/examples/declaration/value/function/operators-0-out.hs
--- a/data/examples/declaration/value/function/operators-0-out.hs
+++ b/data/examples/declaration/value/function/operators-0-out.hs
@@ -1,5 +1,8 @@
+import Control.Lens.Operators
+
 a =
-  b & c .~ d
+  b
+    & c .~ d
     & e
       %~ f
         g
diff --git a/data/examples/declaration/value/function/operators-0.hs b/data/examples/declaration/value/function/operators-0.hs
--- a/data/examples/declaration/value/function/operators-0.hs
+++ b/data/examples/declaration/value/function/operators-0.hs
@@ -1,3 +1,5 @@
+import Control.Lens.Operators
+
 a =
   b & c .~ d
     & e %~ f
diff --git a/data/examples/declaration/value/function/operators-2-out.hs b/data/examples/declaration/value/function/operators-2-out.hs
--- a/data/examples/declaration/value/function/operators-2-out.hs
+++ b/data/examples/declaration/value/function/operators-2-out.hs
@@ -1,4 +1,6 @@
 foo n
-  | x || y && z || n ** x
+  | x
+      || y && z
+      || n ** x
       || x && n =
-    42
+      42
diff --git a/data/examples/declaration/value/function/operators-3-out.hs b/data/examples/declaration/value/function/operators-3-out.hs
--- a/data/examples/declaration/value/function/operators-3-out.hs
+++ b/data/examples/declaration/value/function/operators-3-out.hs
@@ -1,3 +1,8 @@
+import Control.Arrow
+
 foo =
-  op <> n <+> colon <+> prettySe <+> text "="
+  op <> n
+    <+> colon
+    <+> prettySe
+    <+> text "="
     <+> prettySe <> text sc
diff --git a/data/examples/declaration/value/function/operators-3.hs b/data/examples/declaration/value/function/operators-3.hs
--- a/data/examples/declaration/value/function/operators-3.hs
+++ b/data/examples/declaration/value/function/operators-3.hs
@@ -1,3 +1,5 @@
+import Control.Arrow
+
 foo =
   op <> n <+> colon <+> prettySe <+> text "=" <+>
     prettySe <> text sc
diff --git a/data/examples/declaration/value/function/operators-4-out.hs b/data/examples/declaration/value/function/operators-4-out.hs
--- a/data/examples/declaration/value/function/operators-4-out.hs
+++ b/data/examples/declaration/value/function/operators-4-out.hs
@@ -1,3 +1,8 @@
+import Control.Arrow
+
 foo =
-  line <> bindingOf <+> text "=" <+> tPretty <+> colon
+  line <> bindingOf
+    <+> text "="
+    <+> tPretty
+    <+> colon
     <+> align <> prettyPs
diff --git a/data/examples/declaration/value/function/operators-4.hs b/data/examples/declaration/value/function/operators-4.hs
--- a/data/examples/declaration/value/function/operators-4.hs
+++ b/data/examples/declaration/value/function/operators-4.hs
@@ -1,3 +1,5 @@
+import Control.Arrow
+
 foo =
   line <> bindingOf <+> text "=" <+> tPretty <+> colon <+>
     align <> prettyPs
diff --git a/data/examples/declaration/value/function/operators-6-out.hs b/data/examples/declaration/value/function/operators-6-out.hs
--- a/data/examples/declaration/value/function/operators-6-out.hs
+++ b/data/examples/declaration/value/function/operators-6-out.hs
@@ -1,3 +1,5 @@
+import Servant.API
+
 type PermuteRef =
   "a"
     :> ( "b" :> "c" :> End
diff --git a/data/examples/declaration/value/function/operators-6.hs b/data/examples/declaration/value/function/operators-6.hs
--- a/data/examples/declaration/value/function/operators-6.hs
+++ b/data/examples/declaration/value/function/operators-6.hs
@@ -1,3 +1,5 @@
+import Servant.API
+
 type PermuteRef =
        "a" :> (    "b" :> "c" :> End
               :<|> "c" :> "b" :> End
diff --git a/data/examples/declaration/value/function/operators-7-out.hs b/data/examples/declaration/value/function/operators-7-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operators-7-out.hs
@@ -0,0 +1,1 @@
+foo c = case c of { _ -> c } : []
diff --git a/data/examples/declaration/value/function/operators-7.hs b/data/examples/declaration/value/function/operators-7.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operators-7.hs
@@ -0,0 +1,1 @@
+foo c = case c of {_->c} : []
diff --git a/data/examples/declaration/value/function/overloaded-labels-out.hs b/data/examples/declaration/value/function/overloaded-labels-out.hs
--- a/data/examples/declaration/value/function/overloaded-labels-out.hs
+++ b/data/examples/declaration/value/function/overloaded-labels-out.hs
@@ -3,3 +3,36 @@
 foo = #field
 
 bar = (#this) (#that)
+
+baz = #Foo #"Hello world!" #"\"" #3 #"\n"
+
+-- from https://gitlab.haskell.org/ghc/ghc/-/blob/ghc-9.6.1-alpha3/testsuite/tests/overloadedrecflds/should_run/T11671_run.hs
+-- unnecessary once https://github.com/tweag/ormolu/issues/821 lands
+main =
+  traverse_
+    putStrLn
+    [ #a,
+      #number17,
+      #do,
+      #type,
+      #Foo,
+      #3,
+      #"199.4",
+      #17a23b,
+      #f'a',
+      #'a',
+      #',
+      #''notTHSplice,
+      #"...",
+      #привет,
+      #こんにちは,
+      #"3",
+      #":",
+      #"Foo",
+      #"The quick brown fox",
+      #"\"",
+      (++) #hello #world,
+      (++) #"hello" #"world",
+      #"hello" # 1, -- equivalent to `(fromLabel @"hello") # 1`
+      f "hello" #2 -- equivalent to `f ("hello"# :: Addr#) 2`
+    ]
diff --git a/data/examples/declaration/value/function/overloaded-labels.hs b/data/examples/declaration/value/function/overloaded-labels.hs
--- a/data/examples/declaration/value/function/overloaded-labels.hs
+++ b/data/examples/declaration/value/function/overloaded-labels.hs
@@ -2,3 +2,33 @@
 
 foo = #field
 bar = (#this ) ( #that)
+baz = #Foo #"Hello world!" #"\"" #3 #"\n"
+
+-- from https://gitlab.haskell.org/ghc/ghc/-/blob/ghc-9.6.1-alpha3/testsuite/tests/overloadedrecflds/should_run/T11671_run.hs
+-- unnecessary once https://github.com/tweag/ormolu/issues/821 lands
+main = traverse_ putStrLn
+  [ #a
+  , #number17
+  , #do
+  , #type
+  , #Foo
+  , #3
+  , #"199.4"
+  , #17a23b
+  , #f'a'
+  , #'a'
+  , #'
+  , #''notTHSplice
+  , #"..."
+  , #привет
+  , #こんにちは
+  , #"3"
+  , #":"
+  , #"Foo"
+  , #"The quick brown fox"
+  , #"\""
+  , (++) #hello#world
+  , (++) #"hello"#"world"
+  , #"hello"# 1 -- equivalent to `(fromLabel @"hello") # 1`
+  , f "hello"#2 -- equivalent to `f ("hello"# :: Addr#) 2`
+  ]
diff --git a/data/examples/declaration/value/function/parallel-comprehensions-complex-out.hs b/data/examples/declaration/value/function/parallel-comprehensions-complex-out.hs
--- a/data/examples/declaration/value/function/parallel-comprehensions-complex-out.hs
+++ b/data/examples/declaration/value/function/parallel-comprehensions-complex-out.hs
@@ -10,26 +10,26 @@
       i,
       j
     )
-    | a <- -- Foo 1
-        x, -- Foo 2
-      b <- -- Bar 1
-        y, -- Bar 2
+  | a <- -- Foo 1
+      x, -- Foo 2
+    b <- -- Bar 1
+      y, -- Bar 2
+    a
+      `mod` b -- Value
+      == 0
+  | c <- -- Baz 1
+      z
+        * z -- Baz 2
+        -- Baz 3
+  | d <- w -- Other
+  | e <- x * x -- Foo bar
+  | f <- -- Foo baz 1
+      y + y -- Foo baz 2
+  | h <- z + z * w ^ 2 -- Bar foo
+  | i <- -- Bar bar 1
       a
-        `mod` b -- Value
-        == 0
-    | c <- -- Baz 1
-        z
-          * z -- Baz 2
-          -- Baz 3
-    | d <- w -- Other
-    | e <- x * x -- Foo bar
-    | f <- -- Foo baz 1
-        y + y -- Foo baz 2
-    | h <- z + z * w ^ 2 -- Bar foo
-    | i <- -- Bar bar 1
-        a
-          + b, -- Bar bar 2
-          -- Bar bar 3
-      j <- -- Bar baz 1
-        a + b -- Bar baz 2
+        + b, -- Bar bar 2
+        -- Bar bar 3
+    j <- -- Bar baz 1
+      a + b -- Bar baz 2
   ]
diff --git a/data/examples/declaration/value/function/parens-comments-out.hs b/data/examples/declaration/value/function/parens-comments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/parens-comments-out.hs
@@ -0,0 +1,4 @@
+test =
+  ( -- a
+    x
+  )
diff --git a/data/examples/declaration/value/function/parens-comments.hs b/data/examples/declaration/value/function/parens-comments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/parens-comments.hs
@@ -0,0 +1,3 @@
+test = (
+  -- a
+  x)
diff --git a/data/examples/declaration/value/function/pattern/many-guards-in-singleline-out.hs b/data/examples/declaration/value/function/pattern/many-guards-in-singleline-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/many-guards-in-singleline-out.hs
@@ -0,0 +1,3 @@
+foobar x | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7
+
+foobaz | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7
diff --git a/data/examples/declaration/value/function/pattern/many-guards-in-singleline.hs b/data/examples/declaration/value/function/pattern/many-guards-in-singleline.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/many-guards-in-singleline.hs
@@ -0,0 +1,3 @@
+foobar x | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7
+
+foobaz | x <- 5 = 5 | x <- 6 = 6 | otherwise = 7
diff --git a/data/examples/declaration/value/function/pattern/multiline-guard-statement-out.hs b/data/examples/declaration/value/function/pattern/multiline-guard-statement-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/multiline-guard-statement-out.hs
@@ -0,0 +1,6 @@
+foobarbar :: Int -> Bool
+foobarbar
+  | x <-
+      5 = case x of
+      5 -> True
+      _ -> False
diff --git a/data/examples/declaration/value/function/pattern/multiline-guard-statement.hs b/data/examples/declaration/value/function/pattern/multiline-guard-statement.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/multiline-guard-statement.hs
@@ -0,0 +1,5 @@
+foobarbar :: Int -> Bool
+foobarbar | x <-
+    5 = case x of
+  5 -> True
+  _ -> False
diff --git a/data/examples/declaration/value/function/pattern/multiple-guard-statements-out.hs b/data/examples/declaration/value/function/pattern/multiple-guard-statements-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/multiple-guard-statements-out.hs
@@ -0,0 +1,6 @@
+foobarbar :: Int -> Bool
+foobarbar
+  | x <- 5,
+    y <- 6 = case x of
+      5 -> True
+      _ -> False
diff --git a/data/examples/declaration/value/function/pattern/multiple-guard-statements.hs b/data/examples/declaration/value/function/pattern/multiple-guard-statements.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/multiple-guard-statements.hs
@@ -0,0 +1,4 @@
+foobarbar :: Int -> Bool
+foobarbar | x <- 5, y <- 6 = case x of
+  5 -> True
+  _ -> False
diff --git a/data/examples/declaration/value/function/pattern/or-patterns-out.hs b/data/examples/declaration/value/function/pattern/or-patterns-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/or-patterns-out.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE PatternSynonyms #-}
+
+tasty (Cupcake; Cookie) = True
+tasty (Liquorice; Raisins) = False
+
+f :: (Eq a, Show a) => a -> a -> Bool
+f a ((== a) -> True; show -> "yes") = True
+f _ _ = False
+
+small (abs -> (0; 1; 2); 3) = True -- -3 is not small
+small _ = False
+
+type Coll a = Either [a] (Set a)
+
+pattern None <- (Left []; Right (toList -> []))
+
+case e of
+  1; 2; 3 -> x
+  4; (5; 6) -> y
+
+sane e = case e of
+  1
+  2
+  3 ->
+    a
+  4
+  5
+  6 -> b
+  7; 8 -> c
+
+insane e = case e of
+  A _ _
+  B _
+  C -> 3
+  (D; E (Just _) Nothing) ->
+    4
+  F -> 5
+
+food
+  foo@( A;
+        B;
+        C
+        ) = Just foo
+food _ = Nothing
diff --git a/data/examples/declaration/value/function/pattern/or-patterns.hs b/data/examples/declaration/value/function/pattern/or-patterns.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/pattern/or-patterns.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE PatternSynonyms #-}
+
+tasty (Cupcake; Cookie) = True
+tasty (Liquorice; Raisins) = False
+
+f :: (Eq a, Show a) => a -> a -> Bool
+f a ((== a) -> True; show -> "yes") = True
+f _ _ = False
+
+small (abs -> (0; 1; 2); 3) = True -- -3 is not small
+small _ = False
+
+type Coll a = Either [a] (Set a)
+pattern None <- (Left []; Right (toList -> []))
+
+case e of
+  1; 2; 3 -> x
+  4; (5; 6) -> y
+
+sane e = case e of
+  1
+  2
+  3 ->
+    a
+  4
+  5;6 -> b
+  7;8 -> c
+
+insane e = case e of
+  A _ _; B _
+  C -> 3
+  (D; E (Just _) Nothing)
+   -> 4
+  F -> 5
+
+food foo@(A;
+          B;
+          C) = Just foo
+food _ = Nothing
diff --git a/data/examples/declaration/value/function/pragmas-out.hs b/data/examples/declaration/value/function/pragmas-out.hs
--- a/data/examples/declaration/value/function/pragmas-out.hs
+++ b/data/examples/declaration/value/function/pragmas-out.hs
@@ -1,8 +1,13 @@
-sccfoo = {-# SCC foo #-} 1
+sccfoo = {-# SCC "foo" #-} 1
 
 sccbar =
   {-# SCC "barbaz" #-}
   "hello"
+
+foo = do
+  {-# SCC "foo" #-}
+    fmap succ $ do
+      {-# SCC "bar" #-} pure 1
 
 -- CORE pragma got removed in https://gitlab.haskell.org/ghc/ghc/-/commit/12f9035200424ec8104484f154a040d612fee99d
 
diff --git a/data/examples/declaration/value/function/pragmas.hs b/data/examples/declaration/value/function/pragmas.hs
--- a/data/examples/declaration/value/function/pragmas.hs
+++ b/data/examples/declaration/value/function/pragmas.hs
@@ -2,6 +2,10 @@
 sccbar = {-# SCC "barbaz"#-}
   "hello"
 
+foo = do
+  {-# SCC "foo" #-} fmap succ $ do
+    {-# SCC "bar" #-} pure 1
+
 -- CORE pragma got removed in https://gitlab.haskell.org/ghc/ghc/-/commit/12f9035200424ec8104484f154a040d612fee99d
 
 corefoo = {-# CORE "foo"#-}  1
diff --git a/data/examples/declaration/value/function/primitive-literals-out.hs b/data/examples/declaration/value/function/primitive-literals-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/primitive-literals-out.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE ExtendedLiterals #-}
+{-# LANGUAGE MagicHash #-}
+
+foo = 1#
+
+bar = 2##
+
+baz = 3#Word32
+
+baz = 0b1010#Int64
diff --git a/data/examples/declaration/value/function/primitive-literals.hs b/data/examples/declaration/value/function/primitive-literals.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/primitive-literals.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE ExtendedLiterals, MagicHash #-}
+
+foo = 1#
+
+bar = 2##
+
+baz = 3#Word32
+
+baz =  0b1010#Int64
diff --git a/data/examples/declaration/value/function/record/dot-multiline-out.hs b/data/examples/declaration/value/function/record/dot-multiline-out.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/record/dot-multiline-out.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}
-
-bar' =
-  (Foo 1){bar = 2
-         }
-
-fooplus'''' f n =
-  f{foo = n,
-    bar = n
-   }
-
-fooplus''''' f n =
-  f
-    { foo = n
-    }
diff --git a/data/examples/declaration/value/function/record/dot-multiline.hs b/data/examples/declaration/value/function/record/dot-multiline.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/record/dot-multiline.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}
-bar' = (Foo 1){bar = 2
-              }
-
-fooplus'''' f n = f{foo = n,
-                    bar = n
-                   }
-
-fooplus''''' f n = f
-                   { foo = n }
diff --git a/data/examples/declaration/value/function/record/dot-singleline-out.hs b/data/examples/declaration/value/function/record/dot-singleline-out.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/record/dot-singleline-out.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}
-
-data Foo = Foo {bar :: Int}
-
-mfoo = fmap (.bar) $ Nothing
-
-bar = (Foo 1).bar
-
-fooplus f n = f{foo = f.bar + n}
-
-fooplus' f n = f {foo = f.bar + n}
-
-fooplus'' f n = f {foo = f.bar + n}
-
-fooplus''' f n = f {foo = f.bar + n}
diff --git a/data/examples/declaration/value/function/record/dot-singleline.hs b/data/examples/declaration/value/function/record/dot-singleline.hs
deleted file mode 100644
--- a/data/examples/declaration/value/function/record/dot-singleline.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}
-data Foo = Foo { bar :: Int }
-
-mfoo = fmap (.bar)   $ Nothing
-
-bar = (  Foo 1).bar
-
-fooplus f n = f{foo = f.bar + n}
-
-fooplus' f n = f { foo = f.bar + n}
-
-fooplus'' f n = f {foo = f.bar + n}
-
-fooplus''' f n = f{ foo = f.bar + n}
diff --git a/data/examples/declaration/value/function/record/record-dot-out.hs b/data/examples/declaration/value/function/record/record-dot-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/record-dot-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedRecordUpdate #-}
+
+data Foo = Foo {bar :: Foo}
+
+mfoo = fmap (.bar) $ Nothing
+
+bar = (Foo 1).bar
+
+fooplus f n = f {foo = f.bar + n}
+
+nestedFoo f = f.bar.bar.bar.bar.bar
+
+nestedFooUpdate f = f {bar.bar = f.bar} <> f {bar.bar.bar.bar}
+
+operatorUpdate f = f {(+) = 1}
diff --git a/data/examples/declaration/value/function/record/record-dot.hs b/data/examples/declaration/value/function/record/record-dot.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/record-dot.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE OverloadedRecordUpdate #-}
+
+data Foo = Foo { bar :: Foo }
+
+mfoo = fmap (.bar)   $ Nothing
+
+bar = (  Foo 1).bar
+
+fooplus f n = f{foo = f.bar + n}
+
+nestedFoo f = f.bar.bar.bar.bar.bar
+
+nestedFooUpdate f = f {bar.bar = f.bar } <> f {bar.bar.bar.bar}
+
+operatorUpdate f = f { (+) = 1 }
diff --git a/data/examples/declaration/value/function/required-type-arguments-2-out.hs b/data/examples/declaration/value/function/required-type-arguments-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/required-type-arguments-2-out.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+ex1 = f (forall a. Proxy a)
+
+ex2 = f ((ctx) => Int)
+
+ex2' = f ((ctx, ctx') => Int)
+
+ex3 = f (String -> Bool)
+
+long =
+  f
+    ( forall m a.
+      (A a, M m) =>
+      String ->
+      Bool %1 ->
+      Maybe Int ->
+      Maybe
+        (String, Int) %1 ->
+      Word %m ->
+      Text
+    )
diff --git a/data/examples/declaration/value/function/required-type-arguments-2.hs b/data/examples/declaration/value/function/required-type-arguments-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/required-type-arguments-2.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE UnicodeSyntax #-}
+{-# LANGUAGE LinearTypes #-}
+
+ex1  = f (forall a. Proxy a)
+ex2  = f (ctx => Int)
+ex2' = f ((ctx,ctx') => Int)
+ex3  = f (String -> Bool)
+
+long = f (forall m a. (A a, M m) => String
+       -> Bool %1 ->
+          Maybe Int
+       -> Maybe
+             (String,Int)
+        ⊸ Word %m -> Text )
diff --git a/data/examples/declaration/value/function/required-type-arguments-out.hs b/data/examples/declaration/value/function/required-type-arguments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/required-type-arguments-out.hs
@@ -0,0 +1,21 @@
+vshow :: forall a -> (Show a) => a -> String
+vshow t x = show (x :: t)
+
+s1 = vshow Int 42
+
+s2 = vshow Double 42
+
+a1 = f (type (Int -> Bool))
+
+a2 = f (type ((Read T) => T))
+
+a3 = f (type (forall a. a))
+
+a4 = f (type (forall a. (Read a) => String -> a))
+
+foo =
+  f
+    ( type ( Maybe
+               Int
+           )
+    )
diff --git a/data/examples/declaration/value/function/required-type-arguments.hs b/data/examples/declaration/value/function/required-type-arguments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/required-type-arguments.hs
@@ -0,0 +1,13 @@
+vshow :: forall a -> Show a => a -> String
+vshow t x = show (x :: t)
+
+s1 = vshow Int    42
+s2 = vshow Double 42
+
+a1 = f (type (Int -> Bool))
+a2 = f (type (Read T => T))
+a3 = f (type (forall a. a))
+a4 = f (type (forall a. Read a => String -> a))
+
+foo = f (type (Maybe
+            Int))
diff --git a/data/examples/declaration/value/function/strings-out.hs b/data/examples/declaration/value/function/strings-out.hs
--- a/data/examples/declaration/value/function/strings-out.hs
+++ b/data/examples/declaration/value/function/strings-out.hs
@@ -2,9 +2,13 @@
 
 foo = "foobar"
 
-bar = "foo\&barbaz"
+bar = "foo\&bar\ \baz"
 
 baz =
   "foo\
   \bar\
   \baz"
+
+weirdGap = "\65\ \0"
+
+weirdEscape = "\^\ "
diff --git a/data/examples/declaration/value/function/strings.hs b/data/examples/declaration/value/function/strings.hs
--- a/data/examples/declaration/value/function/strings.hs
+++ b/data/examples/declaration/value/function/strings.hs
@@ -5,3 +5,7 @@
 baz = "foo\
       \bar\
     \baz"
+
+weirdGap = "\65\ \0"
+
+weirdEscape = "\^\ "
diff --git a/data/examples/declaration/value/function/type-abstractions-out.hs b/data/examples/declaration/value/function/type-abstractions-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/type-abstractions-out.hs
@@ -0,0 +1,9 @@
+id :: forall a. a -> a
+id @t x = x :: t
+
+f1 :: forall a. a -> forall b. b -> (a, b)
+f1 @a x @b y = (x :: a, y :: b)
+
+f2 =
+  (\ @a x @b y -> (x :: a, y :: b)) ::
+    forall a. a -> forall b. b -> (a, b)
diff --git a/data/examples/declaration/value/function/type-abstractions.hs b/data/examples/declaration/value/function/type-abstractions.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/type-abstractions.hs
@@ -0,0 +1,8 @@
+id :: forall a. a -> a
+id @t x = x :: t
+
+f1 :: forall a. a -> forall b. b -> (a, b)
+f1 @a x @b y = (x :: a, y :: b)
+
+f2 = (\ @a x @b y -> (x :: a, y :: b) )
+    :: forall a. a -> forall b. b -> (a, b)
diff --git a/data/examples/declaration/value/function/type-applications-out.hs b/data/examples/declaration/value/function/type-applications-out.hs
--- a/data/examples/declaration/value/function/type-applications-out.hs
+++ b/data/examples/declaration/value/function/type-applications-out.hs
@@ -14,3 +14,13 @@
     @(HASH TPraosStandardCrypto)
     @ByteString
     "And the lamb lies down on Broadway"
+
+test x = case x of
+  Foo @t -> show @t 0
+  Bar
+    @t
+    @u
+    v ->
+      ""
+
+foo = foo @[k|bar|]
diff --git a/data/examples/declaration/value/function/type-applications.hs b/data/examples/declaration/value/function/type-applications.hs
--- a/data/examples/declaration/value/function/type-applications.hs
+++ b/data/examples/declaration/value/function/type-applications.hs
@@ -11,3 +11,11 @@
   @(HASH TPraosStandardCrypto)
   @ByteString
   "And the lamb lies down on Broadway"
+
+test x = case x of
+  Foo  @t -> show @t 0
+  Bar
+   @t @u v
+    -> ""
+
+foo = foo @[k|bar|]
diff --git a/data/examples/declaration/value/function/unboxed-sums-out.hs b/data/examples/declaration/value/function/unboxed-sums-out.hs
--- a/data/examples/declaration/value/function/unboxed-sums-out.hs
+++ b/data/examples/declaration/value/function/unboxed-sums-out.hs
@@ -1,10 +1,26 @@
 {-# LANGUAGE UnboxedSums #-}
 
+foo :: (# Int | Bool #)
 foo = (# 1 | #)
 
+bar :: (# Int | Int | Int | Int #)
 bar = (# | | 2 | #)
 
+baz ::
+  (#
+    Int |
+    Int |
+    Int |
+    Int |
+    Int |
+    Int |
+    Int |
+    Int |
+    Int
+  #)
 baz =
   (#
     | | | 10 | | | | |
   #)
+
+type UbxPair = (# | #)
diff --git a/data/examples/declaration/value/function/unboxed-sums.hs b/data/examples/declaration/value/function/unboxed-sums.hs
--- a/data/examples/declaration/value/function/unboxed-sums.hs
+++ b/data/examples/declaration/value/function/unboxed-sums.hs
@@ -1,7 +1,16 @@
 {-# LANGUAGE UnboxedSums #-}
 
+foo :: (# Int|Bool #)
 foo = (# 1 | #)
+
+bar :: (#Int|Int|Int|Int #)
 bar = (# | |2| #)
+
+baz :: (# Int |
+  Int | Int | Int | Int | Int | Int |
+  Int | Int #)
 baz = (# |
   | | 10 | | | |
   | #)
+
+type UbxPair = (# |  #)
diff --git a/data/examples/declaration/value/function/unboxed-tuples-out.hs b/data/examples/declaration/value/function/unboxed-tuples-out.hs
--- a/data/examples/declaration/value/function/unboxed-tuples-out.hs
+++ b/data/examples/declaration/value/function/unboxed-tuples-out.hs
@@ -8,3 +8,6 @@
     2,
     3
   #)
+
+unit :: (# #)
+unit = (# #)
diff --git a/data/examples/declaration/value/function/unboxed-tuples.hs b/data/examples/declaration/value/function/unboxed-tuples.hs
--- a/data/examples/declaration/value/function/unboxed-tuples.hs
+++ b/data/examples/declaration/value/function/unboxed-tuples.hs
@@ -5,3 +5,6 @@
     1,
     2,
     3 #)
+
+unit :: (##)
+unit = (##)
diff --git a/data/examples/declaration/value/pattern-synonyms/explicitely-bidirectional-out.hs b/data/examples/declaration/value/pattern-synonyms/explicitely-bidirectional-out.hs
--- a/data/examples/declaration/value/pattern-synonyms/explicitely-bidirectional-out.hs
+++ b/data/examples/declaration/value/pattern-synonyms/explicitely-bidirectional-out.hs
@@ -2,8 +2,7 @@
 
 pattern P a <- C a where P a = C a
 
-pattern HeadC x <-
-  x : xs
+pattern HeadC x <- x : xs
   where
     HeadC x = [x]
 
@@ -12,8 +11,7 @@
   where
     HeadC' x = [x]
 
-pattern Simple <-
-  "Simple"
+pattern Simple <- "Simple"
   where
     Simple = "Complicated"
 
diff --git a/data/examples/declaration/warning/warning-multiline-out.hs b/data/examples/declaration/warning/warning-multiline-out.hs
--- a/data/examples/declaration/warning/warning-multiline-out.hs
+++ b/data/examples/declaration/warning/warning-multiline-out.hs
@@ -7,3 +7,12 @@
   #-}
 test :: IO ()
 test = pure ()
+
+instance
+  {-# WARNING "Don't use" #-}
+  Show G1 where
+  show = "G1"
+
+deriving instance
+    {-# WARNING "to be removed" #-}
+  Eq G2
diff --git a/data/examples/declaration/warning/warning-multiline.hs b/data/examples/declaration/warning/warning-multiline.hs
--- a/data/examples/declaration/warning/warning-multiline.hs
+++ b/data/examples/declaration/warning/warning-multiline.hs
@@ -2,3 +2,11 @@
   foo ["These are bad functions", "Really bad!"] #-}
 test :: IO ()
 test = pure ()
+
+instance
+  {-# WARNING "Don't use" #-}
+  Show G1 where
+  show = "G1"
+
+deriving instance
+  {-# WARNING "to be removed" #-} Eq G2
diff --git a/data/examples/declaration/warning/warning-single-line-out.hs b/data/examples/declaration/warning/warning-single-line-out.hs
--- a/data/examples/declaration/warning/warning-single-line-out.hs
+++ b/data/examples/declaration/warning/warning-single-line-out.hs
@@ -6,8 +6,19 @@
 bar = 3
 {-# DEPRECATED bar "Bar is deprecated" #-}
 
-{-# DEPRECATED baz "Baz is also deprecated" #-}
+{-# DEPRECATED data baz "Baz is also deprecated" #-}
 baz = 5
 
 data Number = Number Dobule
-{-# DEPRECATED Number "Use Scientific instead." #-}
+{-# DEPRECATED type Number "Use Scientific instead." #-}
+
+head (a : _) = a
+{-# WARNING in "x-partial" head "This function is partial..." #-}
+
+instance {-# DEPRECATED "Don't use" #-} Show T1
+
+instance {-# WARNING "Don't use either" #-} Show G1
+
+deriving instance {-# DEPRECATED "to be removed" #-} Eq T2
+
+deriving instance {-# WARNING "to be removed as well" #-} Eq G2
diff --git a/data/examples/declaration/warning/warning-single-line.hs b/data/examples/declaration/warning/warning-single-line.hs
--- a/data/examples/declaration/warning/warning-single-line.hs
+++ b/data/examples/declaration/warning/warning-single-line.hs
@@ -8,8 +8,17 @@
 
 {-# Deprecated bar "Bar is deprecated" #-}
 
-{-# DEPRECATED baz "Baz is also deprecated" #-}
+{-# DEPRECATED data baz "Baz is also deprecated" #-}
 baz = 5
 
 data Number = Number Dobule
-{-# DEPRECATED Number "Use Scientific instead." #-}
+{-# DEPRECATED type Number "Use Scientific instead." #-}
+
+head (a:_) = a
+{-# WARNING in "x-partial" head "This function is partial..." #-}
+
+instance {-# DEPRECATED "Don't use" #-}     Show T1 where
+instance {-# WARNING "Don't use either" #-} Show G1 where
+
+deriving instance {-# DEPRECATED "to be removed" #-}      Eq T2
+deriving instance {-# WARNING "to be removed as well" #-} Eq G2
diff --git a/data/examples/import/data-out.hs b/data/examples/import/data-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/data-out.hs
@@ -0,0 +1,3 @@
+module Bar (data P, T (data P), data f) where
+
+import N (T (data P), data P, data f)
diff --git a/data/examples/import/data.hs b/data/examples/import/data.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/data.hs
@@ -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)
diff --git a/data/examples/import/deprecated-export-multi-line-out.hs b/data/examples/import/deprecated-export-multi-line-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/deprecated-export-multi-line-out.hs
@@ -0,0 +1,7 @@
+module X
+  ( {-# DEPRECATE D(D1) "D1 will not be exposed in a version 0.2 and later" #-}
+    D (D1, D2),
+  )
+where
+
+data D = D1 | D2
diff --git a/data/examples/import/deprecated-export-multi-line.hs b/data/examples/import/deprecated-export-multi-line.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/deprecated-export-multi-line.hs
@@ -0,0 +1,5 @@
+module X
+    ( {-# DEPRECATE D(D1) "D1 will not be exposed in a version 0.2 and later" #-}
+      D(D1, D2)
+    ) where
+data D = D1 | D2
diff --git a/data/examples/import/deprecated-export-single-line-out.hs b/data/examples/import/deprecated-export-single-line-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/deprecated-export-single-line-out.hs
@@ -0,0 +1,3 @@
+module A ({-# DEPRECATED "blah" #-} x) where
+
+x = True
diff --git a/data/examples/import/deprecated-export-single-line.hs b/data/examples/import/deprecated-export-single-line.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/deprecated-export-single-line.hs
@@ -0,0 +1,1 @@
+module A ( {-# DEPRECATED "blah" #-} x ) where { x = True }
diff --git a/data/examples/import/docstrings-after-exports-out.hs b/data/examples/import/docstrings-after-exports-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/docstrings-after-exports-out.hs
@@ -0,0 +1,12 @@
+module Test
+  ( since1, -- ^ @since 1.0
+    since2, -- ^ @since 2.0
+    since3, -- ^ @since 3.0
+    SinceType (..), -- ^ @since 4.0
+    SinceClass (..), -- ^ @since 5.0
+    Multi (..),
+    -- ^ since 6.0
+    -- multi
+    -- line
+  )
+where
diff --git a/data/examples/import/docstrings-after-exports.hs b/data/examples/import/docstrings-after-exports.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/docstrings-after-exports.hs
@@ -0,0 +1,11 @@
+module Test (
+        since1, -- ^ @since 1.0
+        since2  -- ^ @since 2.0
+      , since3  -- ^ @since 3.0
+      , SinceType(..) -- ^ @since 4.0
+      , SinceClass(..) -- ^ @since 5.0
+      , Multi(..)
+        -- ^ since 6.0
+        -- multi
+        -- line
+      ) where
diff --git a/data/examples/import/explicit-level-imports-out.hs b/data/examples/import/explicit-level-imports-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/explicit-level-imports-out.hs
@@ -0,0 +1,13 @@
+{-# 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)
diff --git a/data/examples/import/explicit-level-imports-qualified-post-out.hs b/data/examples/import/explicit-level-imports-qualified-post-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/explicit-level-imports-qualified-post-out.hs
@@ -0,0 +1,5 @@
+{-# LANGUAGE ExplicitLevelImports #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+
+import quote A qualified as QA
+import B quote qualified as QB
diff --git a/data/examples/import/explicit-level-imports-qualified-post.hs b/data/examples/import/explicit-level-imports-qualified-post.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/explicit-level-imports-qualified-post.hs
@@ -0,0 +1,5 @@
+{-# LANGUAGE ExplicitLevelImports #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+
+import qualified B quote as QB
+import quote qualified A as QA
diff --git a/data/examples/import/explicit-level-imports.hs b/data/examples/import/explicit-level-imports.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/explicit-level-imports.hs
@@ -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 ()
diff --git a/data/examples/import/sorted-package-imports-out.hs b/data/examples/import/sorted-package-imports-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/sorted-package-imports-out.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE PackageImports #-}
+
+import D
+import "a" Ab
+import "b" Aa
+import "b" Bb
+import "c" Ba
+import "zzzz" Z
+import "this" Y
diff --git a/data/examples/import/sorted-package-imports.hs b/data/examples/import/sorted-package-imports.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/import/sorted-package-imports.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE PackageImports #-}
+
+import "b" Aa
+import "a" Ab
+import "c" Ba
+import D
+import "b" Bb
+
+import "this" Y
+import "zzzz" Z
diff --git a/data/examples/module-header/empty-haddock-out.hs b/data/examples/module-header/empty-haddock-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/module-header/empty-haddock-out.hs
@@ -0,0 +1,1 @@
+module Test where
diff --git a/data/examples/module-header/empty-haddock.hs b/data/examples/module-header/empty-haddock.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/module-header/empty-haddock.hs
@@ -0,0 +1,3 @@
+-- |
+--
+module Test where
diff --git a/data/examples/module-header/multiline-empty-comment-out.hs b/data/examples/module-header/multiline-empty-comment-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/module-header/multiline-empty-comment-out.hs
@@ -0,0 +1,4 @@
+module Foo
+  ( -- test
+  )
+where
diff --git a/data/examples/module-header/multiline-empty-comment.hs b/data/examples/module-header/multiline-empty-comment.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/module-header/multiline-empty-comment.hs
@@ -0,0 +1,2 @@
+module Foo ( -- test
+           ) where
diff --git a/data/examples/other/argument-comment-out.hs b/data/examples/other/argument-comment-out.hs
--- a/data/examples/other/argument-comment-out.hs
+++ b/data/examples/other/argument-comment-out.hs
@@ -5,12 +5,12 @@
 foo _ = True
 
 foo ::
-  Foo a =>
+  (Foo a) =>
   -- | Foo
   Int ->
   Int
 foo ::
-  Foo a =>
+  (Foo a) =>
   -- | Foo
   Int ->
   Int
diff --git a/data/examples/other/comment-empty-let-out.hs b/data/examples/other/comment-empty-let-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/comment-empty-let-out.hs
@@ -0,0 +1,6 @@
+foo =
+  let
+   in 10
+
+-- a comment
+bar = 20
diff --git a/data/examples/other/comment-empty-let.hs b/data/examples/other/comment-empty-let.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/comment-empty-let.hs
@@ -0,0 +1,6 @@
+foo =
+  let
+   in 10
+
+-- a comment
+bar = 20
diff --git a/data/examples/other/comment-spacing-out.hs b/data/examples/other/comment-spacing-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/comment-spacing-out.hs
@@ -0,0 +1,7 @@
+-- | Something.
+foo ::
+  -- | Foo.
+  Int ->
+  -- | Bar.
+  IO ()
+foo _ = pure () -- comment
diff --git a/data/examples/other/comment-spacing.hs b/data/examples/other/comment-spacing.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/comment-spacing.hs
@@ -0,0 +1,7 @@
+-- |Something.
+foo ::
+  -- |Foo.
+  Int ->
+  -- |Bar.
+  IO ()
+foo _ = pure () --comment
diff --git a/data/examples/other/cpp/continuation-out.hs b/data/examples/other/cpp/continuation-out.hs
--- a/data/examples/other/cpp/continuation-out.hs
+++ b/data/examples/other/cpp/continuation-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 foo :: Int
 foo = 5
 
diff --git a/data/examples/other/cpp/continuation.hs b/data/examples/other/cpp/continuation.hs
--- a/data/examples/other/cpp/continuation.hs
+++ b/data/examples/other/cpp/continuation.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 foo :: Int
 foo = 5
 
diff --git a/data/examples/other/cpp/cpp-and-imports-out.hs b/data/examples/other/cpp/cpp-and-imports-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/cpp-and-imports-out.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE CPP #-}
+
+module Bug where
+
+#ifdef flag
+constant :: Int
+constant = 1312
+#endif
diff --git a/data/examples/other/cpp/cpp-and-imports.hs b/data/examples/other/cpp/cpp-and-imports.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/cpp-and-imports.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE CPP #-}
+
+module Bug where
+
+#ifdef flag
+constant :: Int
+constant = 1312
+#endif
diff --git a/data/examples/other/cpp/lonely-hash-out.hs b/data/examples/other/cpp/lonely-hash-out.hs
--- a/data/examples/other/cpp/lonely-hash-out.hs
+++ b/data/examples/other/cpp/lonely-hash-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 module Main (main) where
 
 import Data.Void
diff --git a/data/examples/other/cpp/lonely-hash.hs b/data/examples/other/cpp/lonely-hash.hs
--- a/data/examples/other/cpp/lonely-hash.hs
+++ b/data/examples/other/cpp/lonely-hash.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 module Main (main) where
 
 import Data.Void
diff --git a/data/examples/other/cpp/separation-0a-out.hs b/data/examples/other/cpp/separation-0a-out.hs
--- a/data/examples/other/cpp/separation-0a-out.hs
+++ b/data/examples/other/cpp/separation-0a-out.hs
@@ -1,7 +1,8 @@
-instance Stream s => Monad (ParsecT e s m) where
+{-# LANGUAGE CPP #-}
+
+instance (Stream s) => Monad (ParsecT e s m) where
   return = pure
   (>>=) = pBind
-
 #if !(MIN_VERSION_base(4,13,0))
   fail   = Fail.fail
 #endif
diff --git a/data/examples/other/cpp/separation-0a.hs b/data/examples/other/cpp/separation-0a.hs
--- a/data/examples/other/cpp/separation-0a.hs
+++ b/data/examples/other/cpp/separation-0a.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 instance Stream s => Monad (ParsecT e s m) where
   return = pure
   (>>=)  = pBind
diff --git a/data/examples/other/cpp/separation-0b-out.hs b/data/examples/other/cpp/separation-0b-out.hs
--- a/data/examples/other/cpp/separation-0b-out.hs
+++ b/data/examples/other/cpp/separation-0b-out.hs
@@ -1,7 +1,8 @@
-instance Stream s => Monad (ParsecT e s m) where
+{-# LANGUAGE CPP #-}
+
+instance (Stream s) => Monad (ParsecT e s m) where
   return = pure
   (>>=) = pBind
-
 #if !(MIN_VERSION_base(4,13,0))
   fail   = Fail.fail
 #endif
diff --git a/data/examples/other/cpp/separation-0b.hs b/data/examples/other/cpp/separation-0b.hs
--- a/data/examples/other/cpp/separation-0b.hs
+++ b/data/examples/other/cpp/separation-0b.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 instance Stream s => Monad (ParsecT e s m) where
   return = pure
   (>>=)  = pBind
diff --git a/data/examples/other/cpp/separation-1a-out.hs b/data/examples/other/cpp/separation-1a-out.hs
--- a/data/examples/other/cpp/separation-1a-out.hs
+++ b/data/examples/other/cpp/separation-1a-out.hs
@@ -1,10 +1,11 @@
+{-# LANGUAGE CPP #-}
+
 decompressingPipe ::
   (PrimMonad m, MonadThrow m, MonadResource m) =>
   CompressionMethod ->
   ConduitT ByteString ByteString m ()
 decompressingPipe Store = C.awaitForever C.yield
 decompressingPipe Deflate = Z.decompress $ Z.WindowBits (-15)
-
 #ifdef ENABLE_BZIP2
 decompressingPipe BZip2   = BZ.bunzip2
 #else
diff --git a/data/examples/other/cpp/separation-1a.hs b/data/examples/other/cpp/separation-1a.hs
--- a/data/examples/other/cpp/separation-1a.hs
+++ b/data/examples/other/cpp/separation-1a.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 decompressingPipe
   :: (PrimMonad m, MonadThrow m, MonadResource m)
   => CompressionMethod
diff --git a/data/examples/other/cpp/separation-1b-out.hs b/data/examples/other/cpp/separation-1b-out.hs
--- a/data/examples/other/cpp/separation-1b-out.hs
+++ b/data/examples/other/cpp/separation-1b-out.hs
@@ -1,10 +1,11 @@
+{-# LANGUAGE CPP #-}
+
 decompressingPipe ::
   (PrimMonad m, MonadThrow m, MonadResource m) =>
   CompressionMethod ->
   ConduitT ByteString ByteString m ()
 decompressingPipe Store = C.awaitForever C.yield
 decompressingPipe Deflate = Z.decompress $ Z.WindowBits (-15)
-
 #ifdef ENABLE_BZIP2
 decompressingPipe BZip2   = BZ.bunzip2
 #else
diff --git a/data/examples/other/cpp/separation-1b.hs b/data/examples/other/cpp/separation-1b.hs
--- a/data/examples/other/cpp/separation-1b.hs
+++ b/data/examples/other/cpp/separation-1b.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 decompressingPipe
   :: (PrimMonad m, MonadThrow m, MonadResource m)
   => CompressionMethod
diff --git a/data/examples/other/cpp/separation-2a-out.hs b/data/examples/other/cpp/separation-2a-out.hs
--- a/data/examples/other/cpp/separation-2a-out.hs
+++ b/data/examples/other/cpp/separation-2a-out.hs
@@ -1,5 +1,6 @@
-ffff, ffffffff :: Natural
+{-# LANGUAGE CPP #-}
 
+ffff, ffffffff :: Natural
 #ifdef HASKELL_ZIP_DEV_MODE
 ffff     = 200
 ffffffff = 5000
diff --git a/data/examples/other/cpp/separation-2a.hs b/data/examples/other/cpp/separation-2a.hs
--- a/data/examples/other/cpp/separation-2a.hs
+++ b/data/examples/other/cpp/separation-2a.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 ffff, ffffffff :: Natural
 #ifdef HASKELL_ZIP_DEV_MODE
 ffff     = 200
diff --git a/data/examples/other/cpp/separation-2b-out.hs b/data/examples/other/cpp/separation-2b-out.hs
--- a/data/examples/other/cpp/separation-2b-out.hs
+++ b/data/examples/other/cpp/separation-2b-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 ffff, ffffffff :: Natural
 #ifdef HASKELL_ZIP_DEV_MODE
 ffff     = 200
diff --git a/data/examples/other/cpp/separation-2b.hs b/data/examples/other/cpp/separation-2b.hs
--- a/data/examples/other/cpp/separation-2b.hs
+++ b/data/examples/other/cpp/separation-2b.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 ffff, ffffffff :: Natural
 #ifdef HASKELL_ZIP_DEV_MODE
 ffff     = 200
diff --git a/data/examples/other/cpp/shifted-out.hs b/data/examples/other/cpp/shifted-out.hs
--- a/data/examples/other/cpp/shifted-out.hs
+++ b/data/examples/other/cpp/shifted-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 sinkData h compression = do
   (uncompressedSize, crc32, compressedSize) <-
     case compression of
diff --git a/data/examples/other/cpp/shifted.hs b/data/examples/other/cpp/shifted.hs
--- a/data/examples/other/cpp/shifted.hs
+++ b/data/examples/other/cpp/shifted.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 sinkData h compression = do
   (uncompressedSize, crc32, compressedSize) <-
     case compression of
diff --git a/data/examples/other/cpp/simple-import-out.hs b/data/examples/other/cpp/simple-import-out.hs
--- a/data/examples/other/cpp/simple-import-out.hs
+++ b/data/examples/other/cpp/simple-import-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 module Main (main) where
 
 import Data.Void
diff --git a/data/examples/other/cpp/simple-import.hs b/data/examples/other/cpp/simple-import.hs
--- a/data/examples/other/cpp/simple-import.hs
+++ b/data/examples/other/cpp/simple-import.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE CPP #-}
+
 module Main (main) where
 
 import Data.Void
diff --git a/data/examples/other/disabling/closing-comment-in-disabled-region-out.hs b/data/examples/other/disabling/closing-comment-in-disabled-region-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/closing-comment-in-disabled-region-out.hs
@@ -0,0 +1,3 @@
+{- ORMOLU_DISABLE -}
+a::String
+a="-}"
diff --git a/data/examples/other/disabling/closing-comment-in-disabled-region.hs b/data/examples/other/disabling/closing-comment-in-disabled-region.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/closing-comment-in-disabled-region.hs
@@ -0,0 +1,3 @@
+{- ORMOLU_DISABLE -}
+a::String
+a="-}"
diff --git a/data/examples/other/disabling/empty-disabled-region-out.hs b/data/examples/other/disabling/empty-disabled-region-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/empty-disabled-region-out.hs
@@ -0,0 +1,7 @@
+{- ORMOLU_DISABLE -}
+{- ORMOLU_ENABLE -}
+foo = 0
+{- ORMOLU_DISABLE -}
+{- ORMOLU_ENABLE -}
+bar = 1
+{- ORMOLU_DISABLE -}
diff --git a/data/examples/other/disabling/empty-disabled-region.hs b/data/examples/other/disabling/empty-disabled-region.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/empty-disabled-region.hs
@@ -0,0 +1,7 @@
+{- ORMOLU_DISABLE -}
+{- ORMOLU_ENABLE -}
+foo = 0
+{- ORMOLU_DISABLE -}
+{- ORMOLU_ENABLE -}
+bar = 1
+{- ORMOLU_DISABLE -}
diff --git a/data/examples/other/disabling/indented-fragment-out.hs b/data/examples/other/disabling/indented-fragment-out.hs
deleted file mode 100644
--- a/data/examples/other/disabling/indented-fragment-out.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-foo =
-  {- ORMOLU_DISABLE -}
-  testCase "Foo" testFoo :
-  testCase "Bar" testBar :
-  testCase "Baz" testBaz :
-  {- ORMOLU_ENABLE -}
-  []
diff --git a/data/examples/other/disabling/indented-fragment.hs b/data/examples/other/disabling/indented-fragment.hs
deleted file mode 100644
--- a/data/examples/other/disabling/indented-fragment.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-foo =
-    {- ORMOLU_DISABLE -}
-    testCase "Foo" testFoo :
-    testCase "Bar" testBar :
-    testCase "Baz" testBaz :
-    {- ORMOLU_ENABLE -}
-    []
diff --git a/data/examples/other/disabling/line-pragma-in-disabled-region-out.hs b/data/examples/other/disabling/line-pragma-in-disabled-region-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/line-pragma-in-disabled-region-out.hs
@@ -0,0 +1,7 @@
+module Foo (foo) where
+
+{- ORMOLU_DISABLE -}
+foo=
+{-# LINE 11 "Abc.hs" #-}
+  this . line $ ((is automatically generated) Haskell<>code)
+{- ORMOLU_ENABLE -}
diff --git a/data/examples/other/disabling/line-pragma-in-disabled-region.hs b/data/examples/other/disabling/line-pragma-in-disabled-region.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/line-pragma-in-disabled-region.hs
@@ -0,0 +1,7 @@
+module Foo(foo) where 
+
+{- ORMOLU_DISABLE -}
+foo=
+{-# LINE 11 "Abc.hs" #-}
+  this . line $ ((is automatically generated) Haskell<>code)
+{- ORMOLU_ENABLE -}
diff --git a/data/examples/other/disabling/normalize-magic-comments-out.hs b/data/examples/other/disabling/normalize-magic-comments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/normalize-magic-comments-out.hs
@@ -0,0 +1,8 @@
+{- ORMOLU_DISABLE -}
+  {-  ORMOLU_DISABLE   -}
+foo=bar
+{- ORMOLU_ENABLE -}
+{-ORMOLU_ENABLE     -}
+bar = baz
+{- ORMOLU_DISABLE -}
+baz=foo
diff --git a/data/examples/other/disabling/normalize-magic-comments.hs b/data/examples/other/disabling/normalize-magic-comments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/normalize-magic-comments.hs
@@ -0,0 +1,8 @@
+  {-ORMOLU_DISABLE   -}
+  {-  ORMOLU_DISABLE   -}
+foo=bar
+{-   ORMOLU_ENABLE-}
+    {-ORMOLU_ENABLE     -}
+bar=baz
+{-   ORMOLU_DISABLE-}
+baz=foo
diff --git a/data/examples/other/disabling/preserve-empty-lines-out.hs b/data/examples/other/disabling/preserve-empty-lines-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/preserve-empty-lines-out.hs
@@ -0,0 +1,11 @@
+{- ORMOLU_DISABLE -}
+bar :: Int
+bar = 2
+
+
+baz :: Int
+baz = 3
+{- ORMOLU_ENABLE -}
+
+foo :: Int
+foo = 1
diff --git a/data/examples/other/disabling/preserve-empty-lines.hs b/data/examples/other/disabling/preserve-empty-lines.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/preserve-empty-lines.hs
@@ -0,0 +1,12 @@
+{- ORMOLU_DISABLE -}
+bar :: Int
+bar = 2
+
+
+baz :: Int
+baz = 3
+{- ORMOLU_ENABLE -}
+
+
+foo :: Int
+foo = 1
diff --git a/data/examples/other/disabling/preserve-trailing-whitespace-out.hs b/data/examples/other/disabling/preserve-trailing-whitespace-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/preserve-trailing-whitespace-out.hs
@@ -0,0 +1,5 @@
+foo = 2
+
+{- ORMOLU_DISABLE -}
+bar = 3  
+{- ORMOLU_ENABLE -}
diff --git a/data/examples/other/disabling/preserve-trailing-whitespace.hs b/data/examples/other/disabling/preserve-trailing-whitespace.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/preserve-trailing-whitespace.hs
@@ -0,0 +1,5 @@
+foo = 2  
+
+{- ORMOLU_DISABLE -}
+bar = 3  
+{- ORMOLU_ENABLE -}
diff --git a/data/examples/other/disabling/suffix-comment-out.hs b/data/examples/other/disabling/suffix-comment-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/suffix-comment-out.hs
@@ -0,0 +1,1 @@
+{- ORMOLU_DISABLE -} -- foo
diff --git a/data/examples/other/disabling/suffix-comment.hs b/data/examples/other/disabling/suffix-comment.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/suffix-comment.hs
@@ -0,0 +1,1 @@
+{- ORMOLU_DISABLE -} -- foo
diff --git a/data/examples/other/empty-forall-out.hs b/data/examples/other/empty-forall-out.hs
--- a/data/examples/other/empty-forall-out.hs
+++ b/data/examples/other/empty-forall-out.hs
@@ -12,7 +12,7 @@
   forall. T x = x
 
 {-# RULES
-"r"
+"r" forall.
   r a =
     ()
   #-}
diff --git a/data/examples/other/empty-haddock-out.hs b/data/examples/other/empty-haddock-out.hs
--- a/data/examples/other/empty-haddock-out.hs
+++ b/data/examples/other/empty-haddock-out.hs
@@ -1,5 +1,9 @@
-module Main where
+module Test
+  ( test,
+  )
+where
 
--- |
-foo :: Int
-foo = 5
+test ::
+  test
+
+data T = T
diff --git a/data/examples/other/empty-haddock.hs b/data/examples/other/empty-haddock.hs
--- a/data/examples/other/empty-haddock.hs
+++ b/data/examples/other/empty-haddock.hs
@@ -1,6 +1,13 @@
-module Main where
+-- |
+module Test
+  ( -- |
+    test,
+  )
+where
 
 -- |
+test ::
+  -- |
+  test
 
-foo :: Int
-foo = 5
+data T = T {- ^ -}
diff --git a/data/examples/other/inline-comment-1-out.hs b/data/examples/other/inline-comment-1-out.hs
--- a/data/examples/other/inline-comment-1-out.hs
+++ b/data/examples/other/inline-comment-1-out.hs
@@ -1,12 +1,16 @@
 showPs env ((n, _, Let _ t v) : bs) =
-  "  " ++ show n ++ " : "
+  "  "
+    ++ show n
+    ++ " : "
     ++ showEnv env ({- normalise ctxt env -} t)
     ++ "   =   "
     ++ showEnv env ({- normalise ctxt env -} v)
     ++ "\n"
     ++ showPs env bs
 showPs env ((n, _, b) : bs) =
-  "  " ++ show n ++ " : "
+  "  "
+    ++ show n
+    ++ " : "
     ++ showEnv env ({- normalise ctxt env -} (binderTy b))
     ++ "\n"
     ++ showPs env bs
diff --git a/data/examples/other/invalid-haddock-1-out.hs b/data/examples/other/invalid-haddock-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-1-out.hs
@@ -0,0 +1,11 @@
+test = undefined
+  where
+    a ::
+      -- \** foo
+      Int ->
+      -- \| misplaced
+      --  blablabla
+      Int
+    -- \|misplaced
+    --  blablabla
+    a = undefined
diff --git a/data/examples/other/invalid-haddock-1.hs b/data/examples/other/invalid-haddock-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-1.hs
@@ -0,0 +1,11 @@
+test = undefined
+  where
+    a ::
+      -- ** foo
+      Int ->
+      -- | misplaced
+      --  blablabla
+      Int
+      -- |misplaced
+      --  blablabla
+    a = undefined
diff --git a/data/examples/other/invalid-haddock-2-out.hs b/data/examples/other/invalid-haddock-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-2-out.hs
@@ -0,0 +1,9 @@
+test = undefined
+  where
+    -- \| Comment
+    -- 2nd invalid line
+    a = undefined
+
+    -- \| A multiline
+    -- \| comment
+    b = b
diff --git a/data/examples/other/invalid-haddock-2.hs b/data/examples/other/invalid-haddock-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-2.hs
@@ -0,0 +1,9 @@
+test = undefined
+  where
+    -- | Comment
+    -- 2nd invalid line
+    a = undefined
+
+    -- | A multiline
+    -- | comment
+    b = b
diff --git a/data/examples/other/invalid-haddock-double-trigger-out.hs b/data/examples/other/invalid-haddock-double-trigger-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-double-trigger-out.hs
@@ -0,0 +1,8 @@
+test =
+  foo
+    -- \|| Invalid haddock, with double trigger
+    -- \|| second line
+    + bar
+    -- \||Invalid haddock 2, with double trigger
+    -- \||second line
+    + baz
diff --git a/data/examples/other/invalid-haddock-double-trigger.hs b/data/examples/other/invalid-haddock-double-trigger.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-double-trigger.hs
@@ -0,0 +1,7 @@
+test = foo
+  -- || Invalid haddock, with double trigger
+  -- || second line
+  + bar
+  -- ||Invalid haddock 2, with double trigger
+  -- ||second line
+  + baz
diff --git a/data/examples/other/invalid-haddock-weird-out.hs b/data/examples/other/invalid-haddock-weird-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-weird-out.hs
@@ -0,0 +1,5 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+foo = foo
+
+-- \|# ${
diff --git a/data/examples/other/invalid-haddock-weird.hs b/data/examples/other/invalid-haddock-weird.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/invalid-haddock-weird.hs
@@ -0,0 +1,3 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+foo = foo -- |# ${
diff --git a/data/examples/other/jsffi-out.hs b/data/examples/other/jsffi-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/jsffi-out.hs
@@ -0,0 +1,1 @@
+{-# LANGUAGE JavaScriptFFI #-}
diff --git a/data/examples/other/jsffi.hs b/data/examples/other/jsffi.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/jsffi.hs
@@ -0,0 +1,1 @@
+{-# language JavaScriptFFI #-}
diff --git a/data/examples/other/multiline-forall-out.hs b/data/examples/other/multiline-forall-out.hs
--- a/data/examples/other/multiline-forall-out.hs
+++ b/data/examples/other/multiline-forall-out.hs
@@ -22,8 +22,7 @@
       )
       (x :: *)
       (y :: *).
-    f x y ->
-    G
+    f x y -> G
 
 f ::
   forall
@@ -32,8 +31,7 @@
     )
     (x :: *)
     (y :: *).
-  f x y ->
-  ()
+  f x y -> ()
 f = const ()
 
 type family T f x y where
diff --git a/data/examples/other/necessary-brackets-out.hs b/data/examples/other/necessary-brackets-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/necessary-brackets-out.hs
@@ -0,0 +1,3 @@
+insertEmDash = Text.concat . map \case { x | x == "--" -> "—"; x -> x } . Text.groupBy ((==) `on` Char.isSpace)
+
+foo f a b c = f do { a } + f do { b } + f do c
diff --git a/data/examples/other/necessary-brackets.hs b/data/examples/other/necessary-brackets.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/necessary-brackets.hs
@@ -0,0 +1,3 @@
+insertEmDash = Text.concat . map \case{ x | x == "--" -> "—"; x -> x } . Text.groupBy ((==) `on` Char.isSpace)
+
+foo f a b c = f do {a} + f do {b} + f do {c}
diff --git a/data/examples/other/no-linear-arrows-out.hs b/data/examples/other/no-linear-arrows-out.hs
deleted file mode 100644
--- a/data/examples/other/no-linear-arrows-out.hs
+++ /dev/null
@@ -1,5 +0,0 @@
-type a % b = (a, b)
-
-type Foo a m b = a % m -> b
-
-type Bar a m b = a % m -> b
diff --git a/data/examples/other/no-linear-arrows.hs b/data/examples/other/no-linear-arrows.hs
deleted file mode 100644
--- a/data/examples/other/no-linear-arrows.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-type a % b = (a,b)
-
-type Foo a m b = a % m -> b
-type Bar a m b = a %m -> b
diff --git a/data/examples/other/pragma-sorting-out.hs b/data/examples/other/pragma-sorting-out.hs
--- a/data/examples/other/pragma-sorting-out.hs
+++ b/data/examples/other/pragma-sorting-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE GHC2021 #-}
+{-# LANGUAGE ApplicativeDo #-}
 {-# LANGUAGE NondecreasingIndentation #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
diff --git a/data/examples/other/pragma-sorting.hs b/data/examples/other/pragma-sorting.hs
--- a/data/examples/other/pragma-sorting.hs
+++ b/data/examples/other/pragma-sorting.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE ApplicativeDo       #-}
+{-# LANGUAGE GHC2021             #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies        #-}
 {-# LANGUAGE NondecreasingIndentation #-}
diff --git a/data/parse-failures/disabling-preserves-error-location.hs b/data/parse-failures/disabling-preserves-error-location.hs
new file mode 100644
--- /dev/null
+++ b/data/parse-failures/disabling-preserves-error-location.hs
@@ -0,0 +1,11 @@
+module Main (main) where
+
+{- ORMOLU_DISABLE -}
+foo Int
+foo Int
+foo 5 EU>>>>
+
+{- ORMOLU_ENABLE -}
+
+main :: IO ()
+main = return <<<
diff --git a/data/parse-failures/line-pragma.hs b/data/parse-failures/line-pragma.hs
new file mode 100644
--- /dev/null
+++ b/data/parse-failures/line-pragma.hs
@@ -0,0 +1,5 @@
+module Foo (foo,bar,baz) where
+bar = foo
+{-# LINE 113 "Abc.hs" #-}
+foo = (some <$> ( automatically $ generated ))))))))) invalid Haskell code
+baz = bar
diff --git a/extract-hackage-info/hackage-info.bin b/extract-hackage-info/hackage-info.bin
new file mode 100644
Binary files /dev/null and b/extract-hackage-info/hackage-info.bin differ
diff --git a/ormolu.cabal b/ormolu.cabal
--- a/ormolu.cabal
+++ b/ormolu.cabal
@@ -1,158 +1,214 @@
-cabal-version:      2.4
-name:               ormolu
-version:            0.2.0.0
-license:            BSD-3-Clause
-license-file:       LICENSE.md
-maintainer:         Mark Karpov <mark.karpov@tweag.io>
-tested-with:        ghc ==8.8.4 ghc ==8.10.4 ghc ==9.0.1
-homepage:           https://github.com/tweag/ormolu
-bug-reports:        https://github.com/tweag/ormolu/issues
-synopsis:           A formatter for Haskell source code
-description:        A formatter for Haskell source code.
-category:           Development, Formatting
-build-type:         Simple
+cabal-version: 2.4
+name: ormolu
+version: 0.8.1.1
+license: BSD-3-Clause
+license-file: LICENSE.md
+maintainer: Mark Karpov <mark.karpov@tweag.io>
+tested-with:
+  ghc ==9.10.2
+  ghc ==9.12.2
+  ghc ==9.14.1
+
+homepage: https://github.com/tweag/ormolu
+bug-reports: https://github.com/tweag/ormolu/issues
+synopsis: A formatter for Haskell source code
+description: A formatter for Haskell source code.
+category: Development, Formatting
+build-type: Simple
 extra-source-files:
-    data/**/*.hs
-    data/**/*.txt
+  data/**/*.cabal
+  data/**/*.hs
+  data/**/*.txt
+  extract-hackage-info/hackage-info.bin
 
 extra-doc-files:
-    CONTRIBUTING.md
-    CHANGELOG.md
-    DESIGN.md
-    README.md
+  CHANGELOG.md
+  CONTRIBUTING.md
+  DESIGN.md
+  README.md
 
 source-repository head
-    type:     git
-    location: https://github.com/tweag/ormolu.git
+  type: git
+  location: https://github.com/tweag/ormolu.git
 
 flag dev
-    description: Turn on development settings.
-    default:     False
-    manual:      True
+  description: Turn on development settings.
+  default: False
+  manual: True
 
 library
-    exposed-modules:
-        Ormolu
-        Ormolu.Config
-        Ormolu.Diff.ParseResult
-        Ormolu.Diff.Text
-        Ormolu.Exception
-        Ormolu.Imports
-        Ormolu.Parser
-        Ormolu.Parser.Anns
-        Ormolu.Parser.CommentStream
-        Ormolu.Parser.Pragma
-        Ormolu.Parser.Result
-        Ormolu.Parser.Shebang
-        Ormolu.Printer
-        Ormolu.Printer.Combinators
-        Ormolu.Printer.Comments
-        Ormolu.Printer.Internal
-        Ormolu.Printer.Meat.Common
-        Ormolu.Printer.Meat.Declaration
-        Ormolu.Printer.Meat.Declaration.Annotation
-        Ormolu.Printer.Meat.Declaration.Class
-        Ormolu.Printer.Meat.Declaration.Data
-        Ormolu.Printer.Meat.Declaration.Default
-        Ormolu.Printer.Meat.Declaration.Foreign
-        Ormolu.Printer.Meat.Declaration.Instance
-        Ormolu.Printer.Meat.Declaration.RoleAnnotation
-        Ormolu.Printer.Meat.Declaration.Rule
-        Ormolu.Printer.Meat.Declaration.Signature
-        Ormolu.Printer.Meat.Declaration.Splice
-        Ormolu.Printer.Meat.Declaration.Type
-        Ormolu.Printer.Meat.Declaration.TypeFamily
-        Ormolu.Printer.Meat.Declaration.Value
-        Ormolu.Printer.Meat.Declaration.Warning
-        Ormolu.Printer.Meat.ImportExport
-        Ormolu.Printer.Meat.Module
-        Ormolu.Printer.Meat.Pragma
-        Ormolu.Printer.Meat.Type
-        Ormolu.Printer.Operators
-        Ormolu.Printer.SpanStream
-        Ormolu.Processing.Common
-        Ormolu.Processing.Cpp
-        Ormolu.Processing.Postprocess
-        Ormolu.Processing.Preprocess
-        Ormolu.Terminal
-        Ormolu.Utils
-
-    hs-source-dirs:   src
-    other-modules:    GHC.DynFlags
-    default-language: Haskell2010
-    build-depends:
-        Diff >=0.4 && <1.0,
-        ansi-terminal >=0.10 && <1.0,
-        base >=4.12 && <5.0,
-        bytestring >=0.2 && <0.12,
-        containers >=0.5 && <0.7,
-        dlist >=0.8 && <2.0,
-        exceptions >=0.6 && <0.11,
-        ghc-lib-parser >=9.0 && <9.1,
-        mtl >=2.0 && <3.0,
-        syb >=0.7 && <0.8,
-        text >=0.2 && <1.3
-
-    if flag(dev)
-        ghc-options:
-            -Wall -Werror -Wcompat -Wincomplete-record-updates
-            -Wincomplete-uni-patterns -Wnoncanonical-monad-instances
-            -Wno-missing-home-modules
+  exposed-modules:
+    Ormolu
+    Ormolu.Config
+    Ormolu.Diff.ParseResult
+    Ormolu.Diff.Text
+    Ormolu.Exception
+    Ormolu.Fixity
+    Ormolu.Fixity.Imports
+    Ormolu.Fixity.Internal
+    Ormolu.Fixity.Parser
+    Ormolu.Fixity.Printer
+    Ormolu.Imports
+    Ormolu.Parser
+    Ormolu.Parser.CommentStream
+    Ormolu.Parser.Pragma
+    Ormolu.Parser.Result
+    Ormolu.Printer
+    Ormolu.Printer.Combinators
+    Ormolu.Printer.Comments
+    Ormolu.Printer.Internal
+    Ormolu.Printer.Meat.Common
+    Ormolu.Printer.Meat.Declaration
+    Ormolu.Printer.Meat.Declaration.Annotation
+    Ormolu.Printer.Meat.Declaration.Class
+    Ormolu.Printer.Meat.Declaration.Data
+    Ormolu.Printer.Meat.Declaration.Default
+    Ormolu.Printer.Meat.Declaration.Foreign
+    Ormolu.Printer.Meat.Declaration.Instance
+    Ormolu.Printer.Meat.Declaration.OpTree
+    Ormolu.Printer.Meat.Declaration.RoleAnnotation
+    Ormolu.Printer.Meat.Declaration.Rule
+    Ormolu.Printer.Meat.Declaration.Signature
+    Ormolu.Printer.Meat.Declaration.Splice
+    Ormolu.Printer.Meat.Declaration.StringLiteral
+    Ormolu.Printer.Meat.Declaration.Type
+    Ormolu.Printer.Meat.Declaration.TypeFamily
+    Ormolu.Printer.Meat.Declaration.Value
+    Ormolu.Printer.Meat.Declaration.Warning
+    Ormolu.Printer.Meat.ImportExport
+    Ormolu.Printer.Meat.Module
+    Ormolu.Printer.Meat.Pragma
+    Ormolu.Printer.Meat.Type
+    Ormolu.Printer.Operators
+    Ormolu.Printer.SpanStream
+    Ormolu.Processing.Common
+    Ormolu.Processing.Cpp
+    Ormolu.Processing.Preprocess
+    Ormolu.Terminal
+    Ormolu.Terminal.QualifiedDo
+    Ormolu.Utils
+    Ormolu.Utils.Cabal
+    Ormolu.Utils.Fixity
+    Ormolu.Utils.IO
 
-    else
-        ghc-options: -O2 -Wall
+  hs-source-dirs: src
+  other-modules: GHC.DynFlags
+  default-language: GHC2021
+  build-depends:
+    Cabal-syntax >=3.16 && <3.17,
+    Diff >=0.4 && <2,
+    MemoTrie >=0.6 && <0.7,
+    ansi-terminal >=0.10 && <1.2,
+    array >=0.5 && <0.6,
+    base >=4.14 && <5,
+    binary >=0.8 && <0.9,
+    bytestring >=0.2 && <0.13,
+    choice >=0.2.4.1 && <0.3,
+    containers >=0.5 && <0.9,
+    directory ^>=1.3,
+    file-embed >=0.0.15 && <0.1,
+    filepath >=1.2 && <1.6,
+    ghc-lib-parser >=9.14 && <9.15,
+    megaparsec >=9,
+    mtl >=2 && <3,
+    syb >=0.7 && <0.8,
+    text >=2.1 && <3,
 
-    if impl(ghc <8.10.0)
-        ghc-options: -fmax-pmcheck-iterations=3000000
+  if flag(dev)
+    ghc-options:
+      -Wall
+      -Werror
+      -Wredundant-constraints
+      -Wpartial-fields
+      -Wunused-packages
+  else
+    ghc-options:
+      -O2
+      -Wall
 
 executable ormolu
-    main-is:          Main.hs
-    hs-source-dirs:   app
-    other-modules:    Paths_ormolu
-    autogen-modules:  Paths_ormolu
-    default-language: Haskell2010
-    build-depends:
-        base >=4.12 && <5.0,
-        filepath >=1.2 && <1.5,
-        ghc-lib-parser >=9.0 && <9.1,
-        gitrev >=1.3 && <1.4,
-        optparse-applicative >=0.14 && <0.17,
-        ormolu,
-        text >=0.2 && <1.3
+  main-is: Main.hs
+  hs-source-dirs: app
+  other-modules: Paths_ormolu
+  autogen-modules: Paths_ormolu
+  default-language: GHC2021
+  build-depends:
+    Cabal-syntax >=3.16 && <3.17,
+    base >=4.12 && <5,
+    containers >=0.5 && <0.9,
+    directory ^>=1.3,
+    filepath >=1.2 && <1.6,
+    ghc-lib-parser >=9.14 && <9.15,
+    optparse-applicative >=0.14 && <0.20,
+    ormolu,
+    text >=2.1 && <3,
+    th-env >=0.1.1 && <0.2,
+    unliftio >=0.2.10 && <0.3,
 
-    if flag(dev)
-        ghc-options:
-            -Wall -Werror -Wcompat -Wincomplete-record-updates
-            -Wincomplete-uni-patterns -Wnoncanonical-monad-instances
-            -optP-Wno-nonportable-include-path
+  -- We use parallelism so we need a threaded runtime to get any
+  -- benefit.
+  ghc-options:
+    -threaded
+    -rtsopts
+    -with-rtsopts=-N
 
-    else
-        ghc-options: -O2 -Wall -rtsopts
+  if flag(dev)
+    ghc-options:
+      -Wall
+      -Werror
+      -Wredundant-constraints
+      -Wpartial-fields
+      -Wunused-packages
+      -Wwarn=unused-packages
+  else
+    ghc-options:
+      -O2
+      -Wall
 
 test-suite tests
-    type:               exitcode-stdio-1.0
-    main-is:            Spec.hs
-    build-tool-depends: hspec-discover:hspec-discover >=2.0 && <3.0
-    hs-source-dirs:     tests
-    other-modules:
-        Ormolu.Diff.TextSpec
-        Ormolu.Parser.OptionsSpec
-        Ormolu.Parser.PragmaSpec
-        Ormolu.PrinterSpec
-
-    default-language:   Haskell2010
-    build-depends:
-        base >=4.12 && <5.0,
-        containers >=0.5 && <0.7,
-        filepath >=1.2 && <1.5,
-        hspec >=2.0 && <3.0,
-        ormolu,
-        path >=0.6 && <0.10,
-        path-io >=1.4.2 && <2.0,
-        text >=0.2 && <1.3
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  build-tool-depends: hspec-discover:hspec-discover >=2 && <3
+  hs-source-dirs: tests
+  other-modules:
+    Ormolu.CabalInfoSpec
+    Ormolu.Diff.TextSpec
+    Ormolu.Fixity.ParserSpec
+    Ormolu.Fixity.PrinterSpec
+    Ormolu.FixitySpec
+    Ormolu.OpTreeSpec
+    Ormolu.Parser.OptionsSpec
+    Ormolu.Parser.ParseFailureSpec
+    Ormolu.Parser.PragmaSpec
+    Ormolu.PrinterSpec
 
-    if flag(dev)
-        ghc-options: -Wall -Werror
+  default-language: GHC2021
+  build-depends:
+    Cabal-syntax >=3.16 && <3.17,
+    QuickCheck >=2.14,
+    base >=4.14 && <5,
+    choice >=0.2.4.1 && <0.3,
+    containers >=0.5 && <0.9,
+    directory ^>=1.3,
+    filepath >=1.2 && <1.6,
+    ghc-lib-parser >=9.14 && <9.15,
+    hspec >=2 && <3,
+    hspec-megaparsec >=2.2,
+    megaparsec >=9,
+    ormolu,
+    path >=0.6 && <0.10,
+    path-io >=1.4.2 && <2,
+    temporary ^>=1.3,
+    text >=2.1 && <3,
 
-    else
-        ghc-options: -O2 -Wall
+  if flag(dev)
+    ghc-options:
+      -Wall
+      -Werror
+      -Wredundant-constraints
+      -Wpartial-fields
+      -Wunused-packages
+  else
+    ghc-options:
+      -O2
+      -Wall
diff --git a/src/GHC/DynFlags.hs b/src/GHC/DynFlags.hs
--- a/src/GHC/DynFlags.hs
+++ b/src/GHC/DynFlags.hs
@@ -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
@@ -24,12 +26,13 @@
       sFileSettings = FileSettings {},
       sTargetPlatform =
         Platform
-          { platformWordSize = PW8,
-            platformMini =
-              PlatformMini
-                { platformMini_arch = ArchUnknown,
-                  platformMini_os = OSUnknown
+          { platformArchOS =
+              ArchOS
+                { -- see https://github.com/tweag/ormolu/issues/1087
+                  archOS_arch = ArchJavaScript,
+                  archOS_OS = OSUnknown
                 },
+            platformWordSize = PW8,
             platformUnregisterised = True,
             platformByteOrder = LittleEndian,
             platformHasGnuNonexecStack = False,
@@ -37,11 +40,15 @@
             platformHasSubsectionsViaSymbols = False,
             platformIsCrossCompiling = False,
             platformLeadingUnderscore = False,
-            platformTablesNextToCode = False
+            platformTablesNextToCode = False,
+            platformHasLibm = False,
+            platform_constants = Nothing
           },
       sPlatformMisc = PlatformMisc {},
-      sPlatformConstants =
-        PlatformConstants {pc_DYNAMIC_BY_DEFAULT = False, pc_WORD_SIZE = 8},
+      sUnitSettings =
+        UnitSettings
+          { unitSettings_baseUnitId = UnitId $ fsLit "ormolu"
+          },
       sToolSettings =
         ToolSettings
           { toolSettings_opt_P_fingerprint = fingerprint0,
@@ -49,8 +56,5 @@
           }
     }
 
-fakeLlvmConfig :: LlvmConfig
-fakeLlvmConfig = LlvmConfig [] []
-
 baseDynFlags :: DynFlags
-baseDynFlags = defaultDynFlags fakeSettings fakeLlvmConfig
+baseDynFlags = defaultDynFlags fakeSettings
diff --git a/src/Ormolu.hs b/src/Ormolu.hs
--- a/src/Ormolu.hs
+++ b/src/Ormolu.hs
@@ -1,15 +1,37 @@
-{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE RecordWildCards #-}
 
--- | A formatter for Haskell source code.
+-- | A formatter for Haskell source code. This module exposes the official
+-- stable API, other modules may be not as reliable.
 module Ormolu
-  ( ormolu,
+  ( -- * Top-level formatting functions
+    ormolu,
     ormoluFile,
     ormoluStdin,
+
+    -- * Configuration
     Config (..),
     ColorMode (..),
     RegionIndices (..),
+    SourceType (..),
     defaultConfig,
+    detectSourceType,
+    refineConfig,
     DynOption (..),
+
+    -- * Cabal info
+    CabalUtils.CabalSearchResult (..),
+    CabalUtils.CabalInfo (..),
+    CabalUtils.getCabalInfoForSourceFile,
+
+    -- * Fixity overrides and module re-exports
+    FixityOverrides,
+    defaultFixityOverrides,
+    ModuleReexports,
+    defaultModuleReexports,
+    getDotOrmoluForSourceFile,
+
+    -- * Working with exceptions
     OrmoluException (..),
     withPrettyOrmoluExceptions,
   )
@@ -18,84 +40,118 @@
 import Control.Exception
 import Control.Monad
 import Control.Monad.IO.Class (MonadIO (..))
+import Data.Map.Strict qualified as Map
+import Data.Maybe (fromMaybe)
+import Data.Set qualified as Set
 import Data.Text (Text)
-import qualified Data.Text as T
+import Data.Text qualified as T
+import Data.Text.IO.Utf8 qualified as T.Utf8
 import Debug.Trace
-import qualified GHC.Driver.CmdLine as GHC
-import qualified GHC.Types.SrcLoc as GHC
+import GHC.Driver.Errors.Types
+import GHC.Types.Error
+import GHC.Types.SrcLoc
+import GHC.Utils.Error
 import Ormolu.Config
 import Ormolu.Diff.ParseResult
 import Ormolu.Diff.Text
 import Ormolu.Exception
+import Ormolu.Fixity
 import Ormolu.Parser
+import Ormolu.Parser.CommentStream (CommentStream (..))
 import Ormolu.Parser.Result
 import Ormolu.Printer
 import Ormolu.Utils (showOutputable)
+import Ormolu.Utils.Cabal qualified as CabalUtils
+import Ormolu.Utils.Fixity (getDotOrmoluForSourceFile)
+import System.FilePath
 
--- | Format a 'String', return formatted version as 'Text'.
+-- | Format a 'Text'.
 --
 -- The function
 --
---     * Takes 'String' because that's what GHC parser accepts.
 --     * 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.
 --     * Throws 'OrmoluException'.
+--
+-- __NOTE__: The caller is responsible for setting the appropriate value in
+-- the 'cfgSourceType' field. Autodetection of source type won't happen
+-- here, see 'detectSourceType'.
 ormolu ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Ormolu configuration
   Config RegionIndices ->
   -- | Location of source file
   FilePath ->
   -- | Input to format
-  String ->
+  Text ->
   m Text
-ormolu cfgWithIndices path str = do
-  let totalLines = length (lines str)
+ormolu cfgWithIndices path originalInput = do
+  let totalLines = length (T.lines originalInput)
       cfg = regionIndicesToDeltas totalLines <$> cfgWithIndices
+      fixityMap =
+        packageFixityMap
+          (overapproximatedDependencies cfg) -- memoized on the set of dependencies
+  when (cfgDebug cfg) $ do
+    traceM $ unwords ["*** CONFIG ***", show cfg]
   (warnings, result0) <-
-    parseModule' cfg OrmoluParsingFailed path str
+    parseModule' cfg fixityMap OrmoluParsingFailed path originalInput
   when (cfgDebug cfg) $ do
-    traceM "warnings:\n"
-    traceM (concatMap showWarn warnings)
-    traceM (prettyPrintParseResult result0)
-  -- We're forcing 'txt' 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 !txt = printModule result0
+    forM_ warnings $ \driverMsg -> do
+      let driverMsgSDoc = formatBulleted $ diagnosticMessage defaultOpts driverMsg
+      traceM $ unwords ["*** WARNING ***", showOutputable driverMsgSDoc]
+    forM_ result0 $ \case
+      ParsedSnippet r -> do
+        let CommentStream comments = prCommentStream r
+        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
   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.
     (_, result1) <-
       parseModule'
         cfg
+        fixityMap
         OrmoluOutputParsingFailed
         path
-        (T.unpack txt)
-    unless (cfgUnsafe cfg) $
-      case diffParseResult result0 result1 of
-        Same -> return ()
-        Different ss -> liftIO $ throwIO (OrmoluASTDiffers path ss)
+        formattedText
+    unless (cfgUnsafe cfg) . liftIO $ do
+      let diff = case diffText originalInput formattedText path of
+            Nothing -> error "AST differs, yet no changes have been introduced"
+            Just x -> x
+      when (length result0 /= length result1) $
+        throwIO (OrmoluASTDiffers diff [])
+      forM_ (result0 `zip` result1) $ \case
+        (ParsedSnippet s, ParsedSnippet s') -> case diffParseResult s s' of
+          Same -> return ()
+          Different ss -> throwIO (OrmoluASTDiffers (selectSpans ss diff) ss)
+        (RawSnippet {}, RawSnippet {}) -> pure ()
+        _ -> throwIO (OrmoluASTDiffers diff [])
     -- Try re-formatting the formatted result to check if we get exactly
     -- the same output.
-    when (cfgCheckIdempotence cfg) $
-      let txt2 = printModule result1
-       in case diffText txt txt2 path of
+    when (cfgCheckIdempotence cfg) . liftIO $
+      let reformattedText = printSnippets (cfgDebug cfg) result1
+       in case diffText formattedText reformattedText path of
             Nothing -> return ()
-            Just diff ->
-              liftIO $
-                throwIO (OrmoluNonIdempotentOutput diff)
-  return txt
+            Just diff -> throwIO (OrmoluNonIdempotentOutput diff)
+  return formattedText
 
 -- | Load a file and format it. The file stays intact and the rendered
 -- version is returned as 'Text'.
 --
--- > ormoluFile cfg path =
--- >   liftIO (readFile path) >>= ormolu cfg path
+-- __NOTE__: The caller is responsible for setting the appropriate value in
+-- the 'cfgSourceType' field. Autodetection of source type won't happen
+-- here, see 'detectSourceType'.
 ormoluFile ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Ormolu configuration
   Config RegionIndices ->
   -- | Location of source file
@@ -103,46 +159,104 @@
   -- | Resulting rendition
   m Text
 ormoluFile cfg path =
-  liftIO (readFile path) >>= ormolu cfg path
+  liftIO (T.Utf8.readFile path) >>= ormolu cfg path
 
 -- | Read input from stdin and format it.
 --
--- > ormoluStdin cfg =
--- >   liftIO (hGetContents stdin) >>= ormolu cfg "<stdin>"
+-- __NOTE__: The caller is responsible for setting the appropriate value in
+-- the 'cfgSourceType' field. Autodetection of source type won't happen
+-- here, see 'detectSourceType'.
 ormoluStdin ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Ormolu configuration
   Config RegionIndices ->
   -- | Resulting rendition
   m Text
 ormoluStdin cfg =
-  liftIO getContents >>= ormolu cfg "<stdin>"
+  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'.
+--
+-- @since 0.5.3.0
+refineConfig ::
+  -- | Source type to use
+  SourceType ->
+  -- | Cabal info for the file, if available
+  Maybe CabalUtils.CabalInfo ->
+  -- | Fixity overrides, if available
+  Maybe FixityOverrides ->
+  -- | Module re-exports, if available
+  Maybe ModuleReexports ->
+  -- | 'Config' to refine
+  Config region ->
+  -- | Refined 'Config'
+  Config region
+refineConfig sourceType mcabalInfo mfixityOverrides mreexports rawConfig =
+  rawConfig
+    { cfgDynOptions = cfgDynOptions rawConfig ++ dynOptsFromCabal,
+      cfgFixityOverrides =
+        FixityOverrides $
+          Map.unions
+            [ unFixityOverrides fixityOverrides,
+              unFixityOverrides (cfgFixityOverrides rawConfig),
+              unFixityOverrides defaultFixityOverrides
+            ],
+      cfgModuleReexports =
+        ModuleReexports $
+          Map.unionsWith
+            (<>)
+            [ unModuleReexports reexports,
+              unModuleReexports (cfgModuleReexports rawConfig),
+              unModuleReexports defaultModuleReexports
+            ],
+      cfgDependencies =
+        Set.union (cfgDependencies rawConfig) depsFromCabal,
+      cfgSourceType = sourceType
+    }
+  where
+    fixityOverrides = fromMaybe defaultFixityOverrides mfixityOverrides
+    reexports = fromMaybe defaultModuleReexports mreexports
+    (dynOptsFromCabal, depsFromCabal) =
+      case mcabalInfo of
+        Nothing ->
+          -- If no cabal info is provided, assume base as a dependency by
+          -- default.
+          ([], defaultDependencies)
+        Just CabalUtils.CabalInfo {..} ->
+          -- It makes sense to take into account the operator info for the
+          -- package itself if we know it, as if it were its own dependency.
+          (ciDynOpts, Set.insert ciPackageName ciDependencies)
+
 ----------------------------------------------------------------------------
 -- Helpers
 
 -- | A wrapper around 'parseModule'.
 parseModule' ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Ormolu configuration
   Config RegionDeltas ->
+  -- | Fixity Map for operators
+  PackageFixityMap ->
   -- | How to obtain 'OrmoluException' to throw when parsing fails
-  (GHC.SrcSpan -> String -> OrmoluException) ->
+  (SrcSpan -> String -> OrmoluException) ->
   -- | File name to use in errors
   FilePath ->
   -- | Actual input for the parser
-  String ->
-  m ([GHC.Warn], ParseResult)
-parseModule' cfg mkException path str = do
-  (warnings, r) <- parseModule cfg path str
+  Text ->
+  m (DriverMessages, [SourceSnippet])
+parseModule' cfg fixityMap mkException path str = do
+  (warnings, r) <- parseModule cfg fixityMap path str
   case r of
     Left (spn, err) -> liftIO $ throwIO (mkException spn err)
     Right x -> return (warnings, x)
 
--- | Pretty-print a 'GHC.Warn'.
-showWarn :: GHC.Warn -> String
-showWarn (GHC.Warn reason l) =
-  unlines
-    [ showOutputable reason,
-      showOutputable l
-    ]
+-- | Detect 'SourceType' based on the file extension.
+detectSourceType :: FilePath -> SourceType
+detectSourceType mpath =
+  if takeExtension mpath == ".hsig"
+    then SignatureSource
+    else ModuleSource
diff --git a/src/Ormolu/Config.hs b/src/Ormolu/Config.hs
--- a/src/Ormolu/Config.hs
+++ b/src/Ormolu/Config.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Configuration options used by the tool.
@@ -7,32 +7,56 @@
     ColorMode (..),
     RegionIndices (..),
     RegionDeltas (..),
+    SourceType (..),
     defaultConfig,
+    overapproximatedDependencies,
     regionIndicesToDeltas,
     DynOption (..),
     dynOptionToLocatedStr,
   )
 where
 
-import qualified GHC.Types.SrcLoc as GHC
+import Data.Map.Strict qualified as Map
+import Data.Set (Set)
+import Data.Set qualified as Set
+import Distribution.Types.PackageName (PackageName)
+import GHC.Generics (Generic)
+import GHC.Types.SrcLoc qualified as GHC
+import Ormolu.Fixity
 import Ormolu.Terminal (ColorMode (..))
 
+-- | Type of sources that can be formatted by Ormolu.
+data SourceType
+  = -- | Consider the input as a regular Haskell module
+    ModuleSource
+  | -- | Consider the input as a Backpack module signature
+    SignatureSource
+  deriving (Eq, Show)
+
 -- | Ormolu configuration.
 data Config region = Config
   { -- | Dynamic options to pass to GHC parser
     cfgDynOptions :: ![DynOption],
+    -- | Fixity overrides
+    cfgFixityOverrides :: !FixityOverrides,
+    -- | Module reexports to take into account when doing fixity resolution
+    cfgModuleReexports :: !ModuleReexports,
+    -- | Known dependencies, if any
+    cfgDependencies :: !(Set PackageName),
     -- | Do formatting faster but without automatic detection of defects
     cfgUnsafe :: !Bool,
     -- | Output information useful for debugging
     cfgDebug :: !Bool,
     -- | Checks if re-formatting the result is idempotent
     cfgCheckIdempotence :: !Bool,
+    -- | How to parse the input (regular haskell module or Backpack file)
+    cfgSourceType :: !SourceType,
     -- | Whether to use colors and other features of ANSI terminals
     cfgColorMode :: !ColorMode,
     -- | Region selection
     cfgRegion :: !region
   }
-  deriving (Eq, Show, Functor)
+  deriving (Eq, Show, Functor, Generic)
 
 -- | Region selection as the combination of start and end line numbers.
 data RegionIndices = RegionIndices
@@ -58,9 +82,13 @@
 defaultConfig =
   Config
     { cfgDynOptions = [],
+      cfgFixityOverrides = defaultFixityOverrides,
+      cfgModuleReexports = defaultModuleReexports,
+      cfgDependencies = Set.empty,
       cfgUnsafe = False,
       cfgDebug = False,
       cfgCheckIdempotence = False,
+      cfgSourceType = ModuleSource,
       cfgColorMode = Auto,
       cfgRegion =
         RegionIndices
@@ -68,6 +96,21 @@
             regionEndLine = Nothing
           }
     }
+
+-- | Return all dependencies of the module. This includes both the declared
+-- dependencies of the component we are working with and all potential
+-- module re-export targets.
+overapproximatedDependencies :: Config region -> Set PackageName
+overapproximatedDependencies Config {..} =
+  Set.union cfgDependencies potentialReexportTargets
+  where
+    potentialReexportTargets =
+      Set.fromList
+        . concatMap toTargetPackages
+        $ Map.elems (unModuleReexports cfgModuleReexports)
+    toTargetPackages = concatMap $ \case
+      (Nothing, _) -> []
+      (Just x, _) -> [x]
 
 -- | Convert 'RegionIndices' into 'RegionDeltas'.
 regionIndicesToDeltas ::
diff --git a/src/Ormolu/Diff/ParseResult.hs b/src/Ormolu/Diff/ParseResult.hs
--- a/src/Ormolu/Diff/ParseResult.hs
+++ b/src/Ormolu/Diff/ParseResult.hs
@@ -1,11 +1,7 @@
-{-# LANGUAGE BangPatterns #-}
--- needed on GHC 9.0 due to simplified subsumption
-{-# LANGUAGE ImpredicativeTypes #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE NamedFieldPuns #-}
-{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeepSubsumption #-}
+{-# LANGUAGE GADTs #-}
 {-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE ViewPatterns #-}
 
 -- | This module allows us to diff two 'ParseResult's.
@@ -16,21 +12,25 @@
 where
 
 import Data.ByteString (ByteString)
+import Data.Foldable
+import Data.Function
 import Data.Generics
+import GHC.Data.FastString (FastString)
 import GHC.Hs
-import GHC.Types.Basic
+import GHC.Types.SourceText
 import GHC.Types.SrcLoc
-import Ormolu.Imports (normalizeImports)
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Result
 import Ormolu.Utils
+import Type.Reflection qualified as TR
 
 -- | Result of comparing two 'ParseResult's.
 data ParseResultDiff
   = -- | Two parse results are the same
     Same
   | -- | Two parse results differ
-    Different [SrcSpan]
+    Different [RealSrcSpan]
+  deriving (Show)
 
 instance Semigroup ParseResultDiff where
   Same <> a = a
@@ -54,96 +54,138 @@
     { prCommentStream = cstream1,
       prParsedSource = hs1
     } =
-    matchIgnoringSrcSpans cstream0 cstream1
-      <> matchIgnoringSrcSpans
-        hs0 {hsmodImports = normalizeImports (hsmodImports hs0)}
-        hs1 {hsmodImports = normalizeImports (hsmodImports hs1)}
+    diffCommentStream cstream0 cstream1
+      <> diffHsModule hs0 hs1
 
--- | Compare two values for equality disregarding the following aspects:
---
---     * 'SrcSpan's
---     * ordering of import lists
---     * style (ASCII vs Unicode) of arrows
---     * LayoutInfo (brace style) in extension fields
-matchIgnoringSrcSpans :: Data a => a -> a -> ParseResultDiff
-matchIgnoringSrcSpans a = genericQuery a
+diffCommentStream :: CommentStream -> CommentStream -> ParseResultDiff
+diffCommentStream (CommentStream cs) (CommentStream cs')
+  | commentLines cs == commentLines cs' = Same
+  | otherwise = Different []
   where
+    commentLines = concatMap (toList . unComment . unLoc)
+
+-- | Compare two modules for equality disregarding certain semantically
+-- irrelevant features like exact print annotations.
+diffHsModule :: HsModule GhcPs -> HsModule GhcPs -> ParseResultDiff
+diffHsModule = genericQuery
+  where
     genericQuery :: GenericQ (GenericQ ParseResultDiff)
     genericQuery x y
       -- 'ByteString' implements 'Data' instance manually and does not
       -- implement 'toConstr', so we have to deal with it in a special way.
       | Just x' <- cast x,
         Just y' <- cast y =
-        if x' == (y' :: ByteString)
-          then Same
-          else Different []
+          if x' == (y' :: ByteString)
+            then Same
+            else Different []
+      | Just rep <- isEpTokenish x,
+        Just rep' <- isEpTokenish y =
+          -- Only check whether the Ep(Uni)Tokens are of the same type; don't
+          -- look at the actual payload (e.g. the location).
+          if rep == rep' then Same else Different []
       | typeOf x == typeOf y,
         toConstr x == toConstr y =
-        mconcat $
-          gzipWithQ
-            ( genericQuery
-                `extQ` srcSpanEq
-                `extQ` commentEq
-                `extQ` sourceTextEq
-                `extQ` hsDocStringEq
-                `extQ` importDeclQualifiedStyleEq
-                `extQ` unicodeArrowStyleEq
-                `extQ` layoutInfoEq
-                `ext2Q` forLocated
-            )
-            x
-            y
+          mconcat $
+            gzipWithQ
+              ( genericQuery
+                  -- EPA-related
+                  `extQ` considerEqual @SrcSpan
+                  `ext1Q` epAnnEq
+                  `extQ` considerEqual @SourceText
+                  `extQ` considerEqual @EpAnnComments -- ~ XCGRHSs GhcPs
+                  `extQ` considerEqual @EpaLocation
+                  `extQ` considerEqual @(Maybe EpaLocation)
+                  `extQ` considerEqual @EpLayout
+                  `extQ` considerEqual @AnnSig
+                  `extQ` considerEqual @HsRuleAnn
+                  `extQ` considerEqual @EpLinear
+                  `extQ` considerEqual @AnnSynDecl
+                  -- 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
+                  `extQ` importDeclQualifiedStyleEq
+                  -- Whether a class has an empty context
+                  `extQ` classDeclCtxEq
+                  -- Whether there are parens around a derived type class
+                  `extQ` derivedTyClsParensEq
+                  -- For better error messages
+                  `ext2Q` forLocated
+              )
+              x
+              y
       | otherwise = Different []
-    srcSpanEq :: SrcSpan -> GenericQ ParseResultDiff
-    srcSpanEq _ _ = Same
-    commentEq :: Comment -> GenericQ ParseResultDiff
-    commentEq (Comment _ x) d =
-      case cast d :: Maybe Comment of
-        Nothing -> Different []
-        Just (Comment _ y) ->
-          if x == y
-            then Same
-            else Different []
-    sourceTextEq :: SourceText -> GenericQ ParseResultDiff
-    sourceTextEq _ _ = Same
-    importDeclQualifiedStyleEq ::
-      ImportDeclQualifiedStyle ->
+
+    -- Return the 'TR.SomeTypeRep' of the type of the given value if it is an
+    -- 'EpToken', an 'EpUniToken', or a list of these.
+    isEpTokenish :: (Typeable a) => a -> Maybe TR.SomeTypeRep
+    isEpTokenish = fmap TR.SomeTypeRep . go . TR.typeOf
+      where
+        go :: TR.TypeRep a -> Maybe (TR.TypeRep a)
+        go rep = case rep of
+          TR.App t t'
+            | Just HRefl <- TR.eqTypeRep t (TR.typeRep @[]) ->
+                TR.App t <$> go t'
+          TR.App (TR.App t _) _ ->
+            rep <$ TR.eqTypeRep t (TR.typeRep @EpUniToken)
+          TR.App t _ ->
+            rep <$ TR.eqTypeRep t (TR.typeRep @EpToken)
+          _ -> Nothing
+
+    considerEqualVia ::
+      forall a.
+      (Typeable a) =>
+      (a -> a -> ParseResultDiff) ->
+      a ->
       GenericQ ParseResultDiff
-    importDeclQualifiedStyleEq d0 d1' =
-      case (d0, cast d1' :: Maybe ImportDeclQualifiedStyle) of
-        (x, Just x') | x == x' -> Same
-        (QualifiedPre, Just QualifiedPost) -> Same
-        (QualifiedPost, Just QualifiedPre) -> Same
-        _ -> Different []
+    considerEqualVia f x (cast -> Just x') = f x x'
+    considerEqualVia _ _ _ = Different []
+
+    considerEqualVia' f =
+      considerEqualVia $ \x x' -> if f x x' then Same else Different []
+
+    considerEqual :: forall a. (Typeable a) => a -> GenericQ ParseResultDiff
+    considerEqual = considerEqualVia $ \_ _ -> Same
+
+    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'
+
     hsDocStringEq :: HsDocString -> GenericQ ParseResultDiff
-    hsDocStringEq str0 str1' =
-      case cast str1' :: Maybe HsDocString of
-        Nothing -> Different []
-        Just str1 ->
-          if splitDocString str0 == splitDocString str1
-            then Same
-            else Different []
+    hsDocStringEq = considerEqualVia' ((==) `on` splitDocString)
+
     forLocated ::
       (Data e0, Data e1) =>
       GenLocated e0 e1 ->
       GenericQ ParseResultDiff
     forLocated x@(L mspn _) y =
-      maybe id appendSpan (cast mspn) (genericQuery x y)
-    appendSpan :: SrcSpan -> ParseResultDiff -> ParseResultDiff
-    appendSpan s (Different ss) | fresh && helpful = Different (s : ss)
+      maybe id appendSpan (cast `ext1Q` (Just . epAnnLoc) $ mspn) (genericQuery x y)
       where
-        fresh = not $ any (`isSubspanOf` s) ss
-        helpful = isGoodSrcSpan s
+        epAnnLoc :: EpAnn ann -> SrcSpan
+        epAnnLoc = locA
+    appendSpan :: SrcSpan -> ParseResultDiff -> ParseResultDiff
+    appendSpan s' d@(Different ss) =
+      case s' of
+        RealSrcSpan s _ ->
+          if not $ any (`isRealSubspanOf` s) ss
+            then Different (s : ss)
+            else d
+        UnhelpfulSpan _ -> d
     appendSpan _ d = d
-    -- as we normalize arrow styles (e.g. -> vs →), we consider them equal here
-    unicodeArrowStyleEq :: HsArrow GhcPs -> GenericQ ParseResultDiff
-    unicodeArrowStyleEq (HsUnrestrictedArrow _) (castArrow -> Just (HsUnrestrictedArrow _)) = Same
-    unicodeArrowStyleEq (HsLinearArrow _) (castArrow -> Just (HsLinearArrow _)) = Same
-    unicodeArrowStyleEq (HsExplicitMult _ t) (castArrow -> Just (HsExplicitMult _ t')) = genericQuery t t'
-    unicodeArrowStyleEq _ _ = Different []
-    castArrow :: Typeable a => a -> Maybe (HsArrow GhcPs)
-    castArrow = cast
-    -- LayoutInfo ~ XClassDecl GhcPs tracks brace information
-    layoutInfoEq :: LayoutInfo -> GenericQ ParseResultDiff
-    layoutInfoEq _ (cast -> Just (_ :: LayoutInfo)) = Same
-    layoutInfoEq _ _ = Different []
+
+    classDeclCtxEq :: TyClDecl GhcPs -> GenericQ ParseResultDiff
+    classDeclCtxEq ClassDecl {tcdCtxt = Just (L _ []), ..} tc' = genericQuery ClassDecl {tcdCtxt = Nothing, ..} tc'
+    classDeclCtxEq tc tc' = genericQuery tc tc'
+
+    derivedTyClsParensEq :: DerivClauseTys GhcPs -> GenericQ ParseResultDiff
+    derivedTyClsParensEq (DctSingle NoExtField sigTy) dct' = genericQuery (DctMulti NoExtField [sigTy]) dct'
+    derivedTyClsParensEq dct dct' = genericQuery dct dct'
diff --git a/src/Ormolu/Diff/Text.hs b/src/Ormolu/Diff/Text.hs
--- a/src/Ormolu/Diff/Text.hs
+++ b/src/Ormolu/Diff/Text.hs
@@ -1,33 +1,44 @@
-{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE QualifiedDo #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | This module allows us to diff two 'Text' values.
 module Ormolu.Diff.Text
   ( TextDiff,
     diffText,
+    selectSpans,
     printTextDiff,
   )
 where
 
-import Control.Monad
-import qualified Data.Algorithm.Diff as D
+import Control.Monad (unless, when)
+import Data.Algorithm.Diff qualified as D
+import Data.Foldable (for_)
+import Data.IntSet (IntSet)
+import Data.IntSet qualified as IntSet
 import Data.Maybe (listToMaybe)
 import Data.Text (Text)
-import qualified Data.Text as T
+import Data.Text qualified as T
+import GHC.Types.SrcLoc
 import Ormolu.Terminal
+import Ormolu.Terminal.QualifiedDo qualified as Term
 
 ----------------------------------------------------------------------------
 -- Types
 
 -- | Result of diffing two 'Text's.
-data TextDiff = TextDiff FilePath DiffList
-  deriving (Eq)
-
-instance Show TextDiff where
-  show (TextDiff path _) = "TextDiff " ++ show path ++ " _"
+data TextDiff = TextDiff
+  { -- | Path to the file that is being diffed
+    textDiffPath :: FilePath,
+    -- | The list of differences
+    textDiffDiffList :: DiffList,
+    -- | Selected lines. Only hunks that contain selected lines will be
+    -- displayed, unless 'textDiffSelectedLines' is empty, in which case the
+    -- whole diff will be displayed.
+    textDiffSelectedLines :: IntSet
+  }
+  deriving (Eq, Show)
 
 -- | List of lines tagged by 'D.Both', 'D.First', or 'D.Second'.
 type DiffList = [D.Diff [Text]]
@@ -43,6 +54,7 @@
     hunkSecondLength :: Int,
     hunkDiff :: DiffList
   }
+  deriving (Show)
 
 ----------------------------------------------------------------------------
 -- API
@@ -60,7 +72,13 @@
 diffText a b path =
   if all isBoth xs
     then Nothing
-    else Just (TextDiff path xs)
+    else
+      Just
+        TextDiff
+          { textDiffPath = path,
+            textDiffDiffList = xs,
+            textDiffSelectedLines = IntSet.empty
+          }
   where
     xs = D.getGroupedDiff (lines' a) (lines' b)
     isBoth = \case
@@ -70,45 +88,59 @@
     -- T.lines ignores trailing blank lines
     lines' = T.splitOn "\n"
 
+-- | Select certain spans in the diff (line numbers are interpreted as
+-- belonging to the “before” state). Only selected spans will be printed.
+selectSpans :: [RealSrcSpan] -> TextDiff -> TextDiff
+selectSpans ss textDiff = textDiff {textDiffSelectedLines = xs}
+  where
+    xs = foldl' addOneSpan (textDiffSelectedLines textDiff) ss
+    addOneSpan linesSoFar s =
+      let start = srcSpanStartLine s
+          end = srcSpanEndLine s
+       in IntSet.union
+            linesSoFar
+            (IntSet.fromAscList [start .. end])
+
 -- | Print the given 'TextDiff' as a 'Term' action. This function tries to
 -- mimic the style of @git diff@.
-printTextDiff :: TextDiff -> Term ()
-printTextDiff (TextDiff path xs) = do
-  (bold . putS) path
+printTextDiff :: TextDiff -> Term
+printTextDiff TextDiff {..} = Term.do
+  (bold . put . T.pack) textDiffPath
   newline
-  forM_ (toHunks (assignLines xs)) $ \Hunk {..} -> do
-    cyan $ do
-      put "@@ -"
-      putS (show hunkFirstStartLine)
-      put ","
-      putS (show hunkFirstLength)
-      put " +"
-      putS (show hunkSecondStartLine)
-      put ","
-      putS (show hunkSecondLength)
-      put " @@"
-    newline
-    forM_ hunkDiff $ \case
-      D.Both ys _ ->
-        forM_ ys $ \y -> do
-          unless (T.null y) $
-            put "  "
-          put y
-          newline
-      D.First ys ->
-        forM_ ys $ \y -> red $ do
-          put "-"
-          unless (T.null y) $
-            put " "
-          put y
-          newline
-      D.Second ys ->
-        forM_ ys $ \y -> green $ do
-          put "+"
-          unless (T.null y) $
-            put " "
-          put y
-          newline
+  for_ (toHunks (assignLines textDiffDiffList)) $ \hunk@Hunk {..} ->
+    when (isSelectedLine textDiffSelectedLines hunk) $ Term.do
+      cyan $ Term.do
+        put "@@ -"
+        putShow hunkFirstStartLine
+        put ","
+        putShow hunkFirstLength
+        put " +"
+        putShow hunkSecondStartLine
+        put ","
+        putShow hunkSecondLength
+        put " @@"
+      newline
+      for_ hunkDiff $ \case
+        D.Both ys _ ->
+          for_ ys $ \y -> Term.do
+            unless (T.null y) $
+              put "  "
+            put y
+            newline
+        D.First ys ->
+          for_ ys $ \y -> red $ Term.do
+            put "-"
+            unless (T.null y) $
+              put " "
+            put y
+            newline
+        D.Second ys ->
+          for_ ys $ \y -> green $ Term.do
+            put "+"
+            unless (T.null y) $
+              put " "
+            put y
+            newline
 
 ----------------------------------------------------------------------------
 -- Helpers
@@ -124,41 +156,38 @@
           let firstInc = length a
               secondInc = length b
               a' =
-                zipWith3
-                  (,,)
+                zip3
                   (iterate (+ 1) firstLine)
                   (iterate (+ 1) secondLine)
                   a
            in go
                 (firstLine + firstInc)
                 (secondLine + secondInc)
-                (acc . ((D.Both a' a') :))
+                (acc . (D.Both a' a' :))
                 xs
         D.First a ->
           let firstInc = length a
               a' =
-                zipWith3
-                  (,,)
+                zip3
                   (iterate (+ 1) firstLine)
                   (repeat secondLine)
                   a
            in go
                 (firstLine + firstInc)
                 secondLine
-                (acc . ((D.First a') :))
+                (acc . (D.First a' :))
                 xs
         D.Second b ->
           let secondInc = length b
               b' =
-                zipWith3
-                  (,,)
+                zip3
                   (repeat firstLine)
                   (iterate (+ 1) secondLine)
                   b
            in go
                 firstLine
                 (secondLine + secondInc)
-                (acc . ((D.Second b') :))
+                (acc . (D.Second b' :))
                 xs
 
 -- | Form 'Hunk's from a 'DiffList''.
@@ -180,8 +209,8 @@
       [] ->
         if gotChanges
           then
-            let p = reverse (take margin bothHistory)
-                currentAcc' = addBothAfter p currentAcc
+            let currentAcc' = addBothAfter p currentAcc
+                p = take margin (reverse bothHistory)
              in case formHunk (currentAcc' []) of
                   Nothing -> hunksAcc []
                   Just hunk -> hunksAcc [hunk]
@@ -204,12 +233,12 @@
           piece ->
             if gotChanges
               then
-                let p = reverse bothHistory
-                    currentAcc' = currentAcc . addBothBefore p (piece :)
+                let currentAcc' = currentAcc . addBothBefore p (piece :)
+                    p = reverse bothHistory
                  in go 0 True hunksAcc currentAcc' [] xs
               else
-                let p = reverse (take margin bothHistory)
-                    currentAcc' = addBothBefore p (piece :)
+                let currentAcc' = addBothBefore p (piece :)
+                    p = reverse (take margin bothHistory)
                  in go 0 True hunksAcc currentAcc' [] xs
     addBothBefore [] acc = acc
     addBothBefore p acc = (D.Both p p :) . acc
@@ -284,3 +313,12 @@
 
 third :: (Int, Int, Text) -> Text
 third (_, _, x) = x
+
+isSelectedLine :: IntSet -> Hunk -> Bool
+isSelectedLine selected Hunk {..} =
+  -- If the set of selected lines is empty, everything is selected.
+  IntSet.null selected
+    || not (IntSet.disjoint selected hunkOriginalLines)
+  where
+    hunkOriginalLines =
+      IntSet.fromAscList (take hunkFirstLength [hunkFirstStartLine ..])
diff --git a/src/Ormolu/Exception.hs b/src/Ormolu/Exception.hs
--- a/src/Ormolu/Exception.hs
+++ b/src/Ormolu/Exception.hs
@@ -1,24 +1,30 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE QualifiedDo #-}
 
 -- | 'OrmoluException' type and surrounding definitions.
 module Ormolu.Exception
   ( OrmoluException (..),
+    printOrmoluException,
     withPrettyOrmoluExceptions,
   )
 where
 
 import Control.Exception
-import Control.Monad (forM_)
+import Data.Foldable (for_)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Text as T
+import Data.List.NonEmpty qualified as NE
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Void (Void)
+import Distribution.Parsec.Error (PError, showPError)
 import GHC.Types.SrcLoc
 import Ormolu.Diff.Text (TextDiff, printTextDiff)
 import Ormolu.Terminal
+import Ormolu.Terminal.QualifiedDo qualified as Term
 import System.Exit (ExitCode (..))
 import System.IO
+import Text.Megaparsec (ParseErrorBundle, errorBundlePretty)
 
 -- | Ormolu exception representing all cases when Ormolu can fail.
 data OrmoluException
@@ -27,59 +33,84 @@
   | -- | Parsing of formatted source code failed
     OrmoluOutputParsingFailed SrcSpan String
   | -- | Original and resulting ASTs differ
-    OrmoluASTDiffers FilePath [SrcSpan]
+    OrmoluASTDiffers TextDiff [RealSrcSpan]
   | -- | Formatted source code is not idempotent
     OrmoluNonIdempotentOutput TextDiff
   | -- | Some GHC options were not recognized
     OrmoluUnrecognizedOpts (NonEmpty String)
-  deriving (Eq, Show)
+  | -- | Cabal file parsing failed
+    OrmoluCabalFileParsingFailed FilePath (NonEmpty PError)
+  | -- | Missing input file path when using stdin input and
+    -- accounting for .cabal files
+    OrmoluMissingStdinInputFile
+  | -- | A parse error in a fixity overrides file
+    OrmoluFixityOverridesParseError (ParseErrorBundle Text Void)
+  deriving (Show)
 
-instance Exception OrmoluException
+instance Exception OrmoluException where
+  displayException = T.unpack . runTermPure . printOrmoluException
 
 -- | Print an 'OrmoluException'.
 printOrmoluException ::
   OrmoluException ->
-  Term ()
+  Term
 printOrmoluException = \case
-  OrmoluParsingFailed s e -> do
-    bold (putSrcSpan s)
+  OrmoluParsingFailed s e -> Term.do
+    bold (putOutputable s)
     newline
     put "  The GHC parser (in Haddock mode) failed:"
     newline
     put "  "
     put (T.pack e)
     newline
-  OrmoluOutputParsingFailed s e -> do
-    bold (putSrcSpan s)
+  OrmoluOutputParsingFailed s e -> Term.do
+    bold (putOutputable s)
     newline
     put "  Parsing of formatted code failed:"
+    newline
     put "  "
     put (T.pack e)
     newline
-  OrmoluASTDiffers path ss -> do
-    putS path
+  OrmoluASTDiffers diff ss -> Term.do
+    printTextDiff diff
     newline
     put "  AST of input and AST of formatted code differ."
     newline
-    forM_ ss $ \s -> do
+    for_ ss $ \s -> Term.do
       put "    at "
-      putSrcSpan s
+      putOutputable s
       newline
     put "  Please, consider reporting the bug."
     newline
-  OrmoluNonIdempotentOutput diff -> do
+    put "  To format anyway, use --unsafe."
+    newline
+  OrmoluNonIdempotentOutput diff -> Term.do
     printTextDiff diff
     newline
     put "  Formatting is not idempotent."
     newline
     put "  Please, consider reporting the bug."
     newline
-  OrmoluUnrecognizedOpts opts -> do
+  OrmoluUnrecognizedOpts opts -> Term.do
     put "The following GHC options were not recognized:"
     newline
     put "  "
-    (putS . unwords . NE.toList) opts
+    (put . T.unwords . map T.pack . NE.toList) opts
     newline
+  OrmoluCabalFileParsingFailed cabalFile parseErrors -> Term.do
+    put "Parsing this .cabal file failed:"
+    newline
+    for_ parseErrors $ \e -> Term.do
+      put . T.pack $ "  " <> showPError cabalFile e
+      newline
+  OrmoluMissingStdinInputFile -> Term.do
+    put "The --stdin-input-file option is necessary when using input"
+    newline
+    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
 -- nicely.
@@ -102,3 +133,6 @@
           OrmoluASTDiffers {} -> 5
           OrmoluNonIdempotentOutput {} -> 6
           OrmoluUnrecognizedOpts {} -> 7
+          OrmoluCabalFileParsingFailed {} -> 8
+          OrmoluMissingStdinInputFile {} -> 9
+          OrmoluFixityOverridesParseError {} -> 10
diff --git a/src/Ormolu/Fixity.hs b/src/Ormolu/Fixity.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Fixity.hs
@@ -0,0 +1,168 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- | Definitions for fixity analysis.
+module Ormolu.Fixity
+  ( OpName,
+    pattern OpName,
+    unOpName,
+    occOpName,
+    FixityDirection (..),
+    FixityInfo (..),
+    defaultFixityInfo,
+    FixityApproximation (..),
+    defaultFixityApproximation,
+    FixityOverrides (..),
+    defaultFixityOverrides,
+    ModuleReexports (..),
+    defaultModuleReexports,
+    PackageFixityMap (..),
+    ModuleFixityMap (..),
+    inferFixity,
+    HackageInfo (..),
+    hackageInfo,
+    defaultDependencies,
+    packageFixityMap,
+    packageFixityMap',
+    moduleFixityMap,
+    applyFixityOverrides,
+  )
+where
+
+import Data.Binary qualified as Binary
+import Data.Binary.Get qualified as Binary
+import Data.ByteString.Lazy qualified as BL
+import Data.FileEmbed (embedFile)
+import Data.List.NonEmpty (NonEmpty)
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as Map
+import Data.MemoTrie (memo)
+import Data.Set (Set)
+import Data.Set qualified as Set
+import Distribution.ModuleName (ModuleName)
+import Distribution.Types.PackageName (PackageName, mkPackageName, unPackageName)
+import Language.Haskell.Syntax.ImpExp (ImportListInterpretation (..))
+import Ormolu.Fixity.Imports (FixityImport (..))
+import Ormolu.Fixity.Internal
+
+-- | The built-in 'HackageInfo' used by Ormolu.
+hackageInfo :: HackageInfo
+hackageInfo =
+  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
+-- file is found or taken into consideration.
+defaultDependencies :: Set PackageName
+defaultDependencies = Set.singleton (mkPackageName "base")
+
+-- | Compute the fixity map that is specific to the package we are formatting.
+packageFixityMap ::
+  -- | Set of packages to select
+  Set PackageName ->
+  -- | Package fixity map
+  PackageFixityMap
+packageFixityMap = packageFixityMap' hackageInfo
+
+-- | The same as 'packageFixityMap', except this specific version of the
+-- function allows the user to specify 'HackageInfo' used to build the final
+-- fixity map.
+packageFixityMap' ::
+  -- | Hackage info
+  HackageInfo ->
+  -- | Set of packages to select
+  Set PackageName ->
+  -- | Package fixity map
+  PackageFixityMap
+packageFixityMap' (HackageInfo m) = memoSet $ \dependencies ->
+  -- The core idea here is to transform:
+  --
+  -- Map PackageName (Map ModuleName (Map OpName FixityInfo))
+  --
+  -- into
+  --
+  -- Map OpName [(PackageName, ModuleName, FixityInfo)]
+  --
+  -- which we accomplish by turning 'Map's into tuples with 'Map.toList' and
+  -- then flattening them with 'flatten :: [(a, [b])] -> [(a, b)]'.
+  --
+  -- The target type results from the need to be able to quickly index by
+  -- the operator name when we do fixity resolution later.
+  PackageFixityMap
+    . Map.mapMaybe NE.nonEmpty
+    . Map.fromListWith (<>)
+    . fmap rearrange
+    . flatten
+    . Map.toList
+    . Map.map (flatten . Map.toList . Map.map Map.toList)
+    $ Map.restrictKeys m dependencies
+  where
+    rearrange (packageName, (moduleName, (opName, fixityInfo))) =
+      (opName, [(packageName, moduleName, fixityInfo)])
+    flatten xs = do
+      (k, vs) <- xs
+      v <- vs
+      return (k, v)
+
+-- | Compute the fixity map that is specific to the module we are formatting.
+moduleFixityMap ::
+  -- | Fixity information selected from dependencies of this package
+  PackageFixityMap ->
+  -- | A simplified representation of the import list in this module
+  [FixityImport] ->
+  -- | Fixity map specific to this module
+  ModuleFixityMap
+moduleFixityMap (PackageFixityMap m) imports =
+  ModuleFixityMap $
+    Map.insert
+      ":"
+      (Given colonFixityInfo)
+      (Map.map FromModuleImports (Map.mapMaybeWithKey select m))
+  where
+    select ::
+      OpName ->
+      NonEmpty (PackageName, ModuleName, FixityInfo) ->
+      Maybe (NonEmpty (FixityQualification, FixityInfo))
+    select opName =
+      let f (packageName, moduleName, fixityInfo) =
+            (,fixityInfo)
+              <$> resolveThroughImports packageName moduleName opName
+       in NE.nonEmpty . concatMap f
+    resolveThroughImports ::
+      PackageName ->
+      ModuleName ->
+      OpName ->
+      [FixityQualification]
+    resolveThroughImports packageName moduleName opName =
+      let doesImportMatch FixityImport {..} =
+            let packageMatches =
+                  case fimportPackage of
+                    Nothing -> True
+                    Just p -> p == packageName
+                moduleMatches =
+                  fimportModule == moduleName
+                opMatches = case fimportList of
+                  Nothing -> True
+                  Just (Exactly, xs) -> opName `elem` xs
+                  Just (EverythingBut, xs) -> opName `notElem` xs
+             in packageMatches && moduleMatches && opMatches
+       in fimportQualified <$> filter doesImportMatch imports
+
+-- | Apply fixity overrides.
+applyFixityOverrides ::
+  -- | User overrides
+  FixityOverrides ->
+  -- | Module fixity map
+  ModuleFixityMap ->
+  -- | Module fixity map with overrides applied
+  ModuleFixityMap
+applyFixityOverrides (FixityOverrides o) (ModuleFixityMap m) =
+  ModuleFixityMap (Map.union (Map.map Given o) m)
+
+memoSet :: (Set PackageName -> v) -> Set PackageName -> v
+memoSet f =
+  memo (f . Set.fromAscList . fmap mkPackageName)
+    . fmap unPackageName
+    . Set.toAscList
diff --git a/src/Ormolu/Fixity/Imports.hs b/src/Ormolu/Fixity/Imports.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Fixity/Imports.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Simplified representation of the import list for the purposes of fixity
+-- inference.
+module Ormolu.Fixity.Imports
+  ( FixityImport (..),
+    extractFixityImports,
+    applyModuleReexports,
+  )
+where
+
+import Data.Bifunctor (second)
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as Map
+import Distribution.ModuleName (ModuleName)
+import Distribution.Types.PackageName
+import GHC.Data.FastString qualified as GHC
+import GHC.Hs hiding (ModuleName, OpName)
+import GHC.Types.Name.Occurrence
+import GHC.Types.PkgQual (RawPkgQual (..))
+import GHC.Types.SourceText (StringLiteral (..))
+import GHC.Types.SrcLoc
+import Ormolu.Fixity.Internal
+import Ormolu.Utils (ghcModuleNameToCabal)
+
+-- | Simplified info about an import.
+data FixityImport = FixityImport
+  { fimportPackage :: !(Maybe PackageName),
+    fimportModule :: !ModuleName,
+    fimportQualified :: !FixityQualification,
+    fimportList :: !(Maybe (ImportListInterpretation, [OpName]))
+  }
+
+-- | Extract 'FixityImport's from the AST.
+extractFixityImports ::
+  [LImportDecl GhcPs] ->
+  [FixityImport]
+extractFixityImports = fmap (extractFixityImport . unLoc)
+
+-- | Extract an individual 'FixityImport'.
+extractFixityImport :: ImportDecl GhcPs -> FixityImport
+extractFixityImport ImportDecl {..} =
+  FixityImport
+    { fimportPackage = case ideclPkgQual of
+        NoRawPkgQual -> Nothing
+        RawPkgQual strLiteral ->
+          Just . mkPackageName . GHC.unpackFS . sl_fs $ strLiteral,
+      fimportModule = ideclName',
+      fimportQualified = case (ideclQualified, ideclAs') of
+        (QualifiedPre, Nothing) ->
+          OnlyQualified ideclName'
+        (QualifiedPost, Nothing) ->
+          OnlyQualified ideclName'
+        (QualifiedPre, Just m) -> OnlyQualified m
+        (QualifiedPost, Just m) -> OnlyQualified m
+        (NotQualified, Nothing) ->
+          UnqualifiedAndQualified ideclName'
+        (NotQualified, Just m) ->
+          UnqualifiedAndQualified m,
+      fimportList =
+        fmap
+          (second (concatMap (fmap occOpName . ieToOccNames . unLoc) . unLoc))
+          ideclImportList
+    }
+  where
+    ideclName' = ghcModuleNameToCabal (unLoc ideclName)
+    ideclAs' = ghcModuleNameToCabal . unLoc <$> ideclAs
+
+ieToOccNames :: IE GhcPs -> [OccName]
+ieToOccNames = \case
+  IEVar _ (L _ x) _ -> [occName x]
+  IEThingAbs _ (L _ x) _ -> [occName x]
+  IEThingAll _ (L _ x) _ -> [occName x] -- TODO not quite correct, but how to do better?
+  IEThingWith _ (L _ x) _ xs _ -> occName x : fmap (occName . unLoc) xs
+  _ -> []
+
+-- | Apply given module re-exports.
+applyModuleReexports :: ModuleReexports -> [FixityImport] -> [FixityImport]
+applyModuleReexports (ModuleReexports reexports) imports = imports >>= expand
+  where
+    expand i = do
+      case Map.lookup (fimportModule i) reexports of
+        Nothing -> pure i
+        Just exports ->
+          let exportToImport (mpackageName, mmodule) =
+                i
+                  { fimportPackage = mpackageName,
+                    fimportModule = mmodule
+                  }
+           in NE.toList exports >>= expand . exportToImport
diff --git a/src/Ormolu/Fixity/Internal.hs b/src/Ormolu/Fixity/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Fixity/Internal.hs
@@ -0,0 +1,343 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Ormolu.Fixity.Internal
+  ( OpName,
+    pattern OpName,
+    unOpName,
+    occOpName,
+    FixityDirection (..),
+    FixityInfo (..),
+    colonFixityInfo,
+    defaultFixityInfo,
+    FixityApproximation (..),
+    defaultFixityApproximation,
+    HackageInfo (..),
+    FixityOverrides (..),
+    defaultFixityOverrides,
+    ModuleReexports (..),
+    defaultModuleReexports,
+    PackageFixityMap (..),
+    ModuleFixityMap (..),
+    FixityProvenance (..),
+    FixityQualification (..),
+    inferFixity,
+  )
+where
+
+import Data.Binary (Binary)
+import Data.Binary qualified as Binary
+import Data.Binary.Get qualified as Binary
+import Data.Binary.Put qualified as Binary
+import Data.ByteString.Short (ShortByteString)
+import Data.ByteString.Short qualified as SBS
+import Data.Choice (Choice)
+import Data.Choice qualified as Choice
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict (Map)
+import Data.Map.Strict qualified as Map
+import Data.Maybe (fromMaybe)
+import Data.String (IsString (..))
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Debug.Trace (trace)
+import Distribution.ModuleName (ModuleName)
+import Distribution.Types.PackageName
+import GHC.Data.FastString (fs_sbs)
+import GHC.Generics (Generic)
+import GHC.Types.Name (OccName (occNameFS))
+import GHC.Types.Name.Reader (RdrName (..), rdrNameOcc)
+import Ormolu.Utils (ghcModuleNameToCabal)
+
+-- | An operator name.
+newtype OpName = MkOpName
+  { -- | Invariant: UTF-8 encoded
+    getOpName :: ShortByteString
+  }
+  deriving newtype (Eq, Ord, Binary)
+
+-- | Convert an 'OpName' to 'Text'.
+unOpName :: OpName -> Text
+unOpName = T.decodeUtf8 . SBS.fromShort . getOpName
+
+pattern OpName :: Text -> OpName
+pattern OpName opName <- (unOpName -> opName)
+  where
+    OpName = MkOpName . SBS.toShort . T.encodeUtf8
+
+{-# COMPLETE OpName #-}
+
+-- | Convert an 'OccName to an 'OpName'.
+occOpName :: OccName -> OpName
+occOpName = MkOpName . fs_sbs . occNameFS
+
+instance Show OpName where
+  show = T.unpack . unOpName
+
+instance IsString OpName where
+  fromString = OpName . T.pack
+
+-- | Fixity direction.
+data FixityDirection
+  = InfixL
+  | InfixR
+  | InfixN
+  deriving stock (Eq, Ord, Show, Generic)
+  deriving anyclass (Binary)
+
+-- | Fixity information about an infix operator. This type provides precise
+-- information as opposed to 'FixityApproximation'.
+data FixityInfo = FixityInfo
+  { -- | Fixity direction
+    fiDirection :: FixityDirection,
+    -- | Precedence
+    fiPrecedence :: Double
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+
+instance Binary FixityInfo where
+  put FixityInfo {..} = do
+    Binary.put fiDirection
+    Binary.putDoublele fiPrecedence
+
+  get = do
+    fiDirection <- Binary.get
+    fiPrecedence <- Binary.getDoublele
+    pure FixityInfo {..}
+
+-- | Fixity info of the built-in colon data constructor.
+colonFixityInfo :: FixityInfo
+colonFixityInfo = FixityInfo InfixR 5
+
+-- | Fixity that is implicitly assumed if no fixity declaration is present.
+defaultFixityInfo :: FixityInfo
+defaultFixityInfo = FixityInfo InfixL 9
+
+-- | Approximation of fixity information that takes the uncertainty that can
+-- arise from conflicting definitions into account.
+data FixityApproximation = FixityApproximation
+  { -- | Fixity direction if it is known
+    faDirection :: Maybe FixityDirection,
+    -- | Minimum precedence level found in the (maybe conflicting)
+    -- definitions for the operator (inclusive)
+    faMinPrecedence :: Double,
+    -- | Maximum precedence level found in the (maybe conflicting)
+    -- definitions for the operator (inclusive)
+    faMaxPrecedence :: Double
+  }
+  deriving stock (Eq, Ord, Show, Generic)
+
+instance Binary FixityApproximation where
+  put FixityApproximation {..} = do
+    Binary.put faDirection
+    Binary.putDoublele faMinPrecedence
+    Binary.putDoublele faMaxPrecedence
+
+  get = do
+    faDirection <- Binary.get
+    faMinPrecedence <- Binary.getDoublele
+    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.
+instance Semigroup FixityApproximation where
+  FixityApproximation {faDirection = dir1, faMinPrecedence = min1, faMaxPrecedence = max1}
+    <> FixityApproximation {faDirection = dir2, faMinPrecedence = min2, faMaxPrecedence = max2} =
+      FixityApproximation
+        { faDirection = dir',
+          faMinPrecedence = min min1 min2,
+          faMaxPrecedence = max max1 max2
+        }
+      where
+        dir' = case (dir1, dir2) of
+          (Just a, Just b) | a == b -> Just a
+          _ -> Nothing
+
+-- | The lowest level of information we can have about an operator.
+defaultFixityApproximation :: FixityApproximation
+defaultFixityApproximation = fixityInfoToApproximation defaultFixityInfo
+
+-- | Convert from 'FixityInfo' to 'FixityApproximation'.
+fixityInfoToApproximation :: FixityInfo -> FixityApproximation
+fixityInfoToApproximation FixityInfo {..} =
+  FixityApproximation
+    { faDirection = Just fiDirection,
+      faMinPrecedence = fiPrecedence,
+      faMaxPrecedence = fiPrecedence
+    }
+
+-- | The map of operators declared by each package grouped by module name.
+newtype HackageInfo
+  = HackageInfo (Map PackageName (Map ModuleName (Map OpName FixityInfo)))
+  deriving stock (Generic)
+  deriving anyclass (Binary)
+
+-- | Map from the operator name to its 'FixityInfo'.
+newtype FixityOverrides = FixityOverrides
+  { unFixityOverrides :: Map OpName FixityInfo
+  }
+  deriving stock (Eq, Show)
+
+-- | Fixity overrides to use by default.
+defaultFixityOverrides :: FixityOverrides
+defaultFixityOverrides = FixityOverrides Map.empty
+
+-- | Module re-exports
+newtype ModuleReexports = ModuleReexports
+  { unModuleReexports :: Map ModuleName (NonEmpty (Maybe PackageName, ModuleName))
+  }
+  deriving stock (Eq, Show)
+
+-- | 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
+
+-- | Fixity information that is specific to a package being formatted. It
+-- requires module-specific imports in order to be usable.
+newtype PackageFixityMap
+  = PackageFixityMap (Map OpName (NonEmpty (PackageName, ModuleName, FixityInfo)))
+  deriving stock (Eq, Show)
+
+-- | Fixity map that takes into account imports in a particular module.
+newtype ModuleFixityMap
+  = ModuleFixityMap (Map OpName FixityProvenance)
+  deriving stock (Eq, Show)
+
+-- | Provenance of fixity info.
+data FixityProvenance
+  = -- | 'FixityInfo' of a built-in operator or provided by a user override.
+    Given FixityInfo
+  | -- | 'FixityInfo' to be inferred from module imports.
+    FromModuleImports (NonEmpty (FixityQualification, FixityInfo))
+  deriving stock (Eq, Show)
+
+-- | Fixity qualification that determines how 'FixityInfo' matches a
+-- particular use of an operator, given whether it is qualified or
+-- unqualified and the module name used.
+data FixityQualification
+  = UnqualifiedAndQualified ModuleName
+  | OnlyQualified ModuleName
+  deriving stock (Eq, Show)
+
+-- | Get a 'FixityApproximation' of an operator.
+inferFixity ::
+  -- | Whether to print debug info regarding fixity inference
+  Choice "debug" ->
+  -- | Operator name
+  RdrName ->
+  -- | Module fixity map
+  ModuleFixityMap ->
+  -- | The resulting fixity approximation
+  FixityApproximation
+inferFixity debug rdrName (ModuleFixityMap m) =
+  if Choice.isTrue debug
+    then
+      trace
+        (renderFixityJustification opName moduleName m result)
+        result
+    else result
+  where
+    result =
+      case Map.lookup opName m of
+        Nothing -> defaultFixityApproximation
+        Just (Given fixityInfo) ->
+          fixityInfoToApproximation fixityInfo
+        Just (FromModuleImports xs) ->
+          let isMatching (provenance, _fixityInfo) =
+                case provenance of
+                  UnqualifiedAndQualified mn ->
+                    maybe True (== mn) moduleName
+                  OnlyQualified mn ->
+                    maybe False (== mn) moduleName
+           in fromMaybe defaultFixityApproximation
+                . foldMap (Just . fixityInfoToApproximation . snd)
+                $ NE.filter isMatching xs
+    opName = occOpName (rdrNameOcc rdrName)
+    moduleName = case rdrName of
+      Qual x _ -> Just (ghcModuleNameToCabal x)
+      _ -> Nothing
+
+-- | Render a human-readable account of why a certain 'FixityApproximation'
+-- was chosen for an operator.
+renderFixityJustification ::
+  -- | Operator name
+  OpName ->
+  -- | Qualification of the operator name
+  Maybe ModuleName ->
+  -- | Module fixity map
+  Map OpName FixityProvenance ->
+  -- | The chosen fixity approximation
+  FixityApproximation ->
+  String
+renderFixityJustification opName mqualification m approximation =
+  concat
+    [ "FIXITY analysis of ",
+      show opName,
+      case mqualification of
+        Nothing -> ""
+        Just mn -> " qualified in " ++ show mn,
+      "\n  Provenance: " ++ show (Map.lookup opName m),
+      "\n  Inferred: " ++ show approximation
+    ]
diff --git a/src/Ormolu/Fixity/Parser.hs b/src/Ormolu/Fixity/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Fixity/Parser.hs
@@ -0,0 +1,195 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Parser for fixity maps.
+module Ormolu.Fixity.Parser
+  ( parseDotOrmolu,
+    parseFixityDeclaration,
+    parseModuleReexportDeclaration,
+
+    -- * Raw parsers
+    pFixity,
+    pOperator,
+    pModuleName,
+    pPackageName,
+
+    -- * Internal
+    isIdentifierFirstChar,
+    isIdentifierConstituent,
+    isOperatorConstituent,
+    isPackageNameConstituent,
+    isModuleSegmentFirstChar,
+    isModuleSegmentConstituent,
+  )
+where
+
+import Control.Monad (void, when)
+import Data.Bifunctor (bimap)
+import Data.Char (isAlphaNum, isUpper)
+import Data.Char qualified as Char
+import Data.Either (partitionEithers)
+import Data.List (intercalate)
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as Map
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Void (Void)
+import Distribution.ModuleName (ModuleName)
+import Distribution.ModuleName qualified as ModuleName
+import Distribution.Types.PackageName (PackageName, mkPackageName)
+import Ormolu.Fixity
+import Text.Megaparsec
+import Text.Megaparsec.Char
+import Text.Megaparsec.Char.Lexer qualified as L
+
+type Parser = Parsec Void Text
+
+-- TODO Support fixity namespacing?
+-- https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0065-type-infix.rst
+--
+-- Note that currently, our fixity machinery does *not* do any namespacing:
+--
+--  - https://github.com/tweag/ormolu/pull/994#pullrequestreview-1396958951
+--    brought this up in the past
+--
+--  - https://github.com/tweag/ormolu/pull/1029#issue-1718217029
+--    has a concrete example (morley-prelude) where namespacing would matter
+
+-- | Parse textual representation of 'FixityOverrides'.
+parseDotOrmolu ::
+  -- | Location of the file we are parsing (only for parse errors)
+  FilePath ->
+  -- | File contents to parse
+  Text ->
+  -- | Parse result
+  Either (ParseErrorBundle Text Void) (FixityOverrides, ModuleReexports)
+parseDotOrmolu = runParser pDotOrmolu
+
+-- | Parse a single self-contained fixity declaration.
+parseFixityDeclaration ::
+  -- | Text to parse
+  Text ->
+  -- | Parse result
+  Either (ParseErrorBundle Text Void) [(OpName, FixityInfo)]
+parseFixityDeclaration = runParser (pFixity <* eof) ""
+
+-- | Parse a single self-contained module re-export declaration.
+parseModuleReexportDeclaration ::
+  -- | Text to parse
+  Text ->
+  -- | Parse result
+  Either
+    (ParseErrorBundle Text Void)
+    (ModuleName, NonEmpty (Maybe PackageName, ModuleName))
+parseModuleReexportDeclaration = runParser (pModuleReexport <* eof) ""
+
+pDotOrmolu :: Parser (FixityOverrides, ModuleReexports)
+pDotOrmolu =
+  bimap
+    (FixityOverrides . Map.fromList . mconcat)
+    (ModuleReexports . Map.map NE.sort . Map.fromListWith (<>))
+    . partitionEithers
+    <$> (configLine `sepEndBy` (void eol *> hidden space))
+    <* eof
+  where
+    configLine = eitherP pFixity pModuleReexport
+
+-- | Parse a single fixity declaration, such as
+--
+-- > infixr 4 +++, >>>
+pFixity :: Parser [(OpName, FixityInfo)]
+pFixity = do
+  fiDirection <- pFixityDirection
+  hidden hspace1
+  offsetAtPrecedence <- getOffset
+  fiPrecedence <-
+    try L.float
+      <|> (fromIntegral <$> (L.decimal :: Parser Integer))
+  when (fiPrecedence > 9) $
+    region
+      (setErrorOffset offsetAtPrecedence)
+      (fail "precedence should not be greater than 9")
+  hidden hspace1
+  ops <- sepBy1 pOperator (char ',' >> hidden hspace)
+  hidden hspace
+  let fixityInfo = FixityInfo {..}
+  return ((,fixityInfo) <$> ops)
+
+pFixityDirection :: Parser FixityDirection
+pFixityDirection =
+  choice
+    [ InfixL <$ string "infixl",
+      InfixR <$ string "infixr",
+      InfixN <$ string "infix"
+    ]
+
+-- | See <https://www.haskell.org/onlinereport/haskell2010/haskellch2.html>
+pOperator :: Parser OpName
+pOperator = OpName <$> (tickedOperator <|> normalOperator)
+  where
+    tickedOperator = between tick tick haskellIdentifier
+    tick = char '`'
+    haskellIdentifier =
+      T.cons
+        <$> letterChar
+        <*> takeWhileP Nothing isIdentifierConstituent
+    normalOperator =
+      takeWhile1P (Just "operator character") isOperatorConstituent
+
+pModuleReexport :: Parser (ModuleName, NonEmpty (Maybe PackageName, ModuleName))
+pModuleReexport = do
+  void (string "module")
+  hidden hspace1
+  exportingModule <- pModuleName
+  hidden hspace1
+  void (string "exports")
+  hidden hspace1
+  mexportedPackage <-
+    optional $
+      between (char '\"') (char '\"') pPackageName <* hidden hspace1
+  exportedModule <- pModuleName
+  hidden hspace
+  return (exportingModule, NE.singleton (mexportedPackage, exportedModule))
+
+pModuleName :: Parser ModuleName
+pModuleName =
+  ModuleName.fromString . intercalate "."
+    <$> sepBy1 pModuleSegment (char '.')
+    <?> "module name"
+  where
+    pModuleSegment = do
+      x <- satisfy isModuleSegmentFirstChar <?> "capital letter"
+      xs <-
+        many
+          ( satisfy isModuleSegmentConstituent
+              <?> "module segment continuation"
+          )
+      return (x : xs)
+
+pPackageName :: Parser PackageName
+pPackageName =
+  mkPackageName <$> some (satisfy isPackageNameConstituent) <?> "package name"
+
+-- Internal predicates (exposed for testing)
+
+isIdentifierFirstChar :: Char -> Bool
+isIdentifierFirstChar = Char.isLetter
+
+isIdentifierConstituent :: Char -> Bool
+isIdentifierConstituent x = Char.isAlphaNum x || x == '_' || x == '\''
+
+isOperatorConstituent :: Char -> Bool
+isOperatorConstituent x =
+  (Char.isSymbol x || Char.isPunctuation x)
+    && (x /= ',' && x /= '`' && x /= '(' && x /= ')')
+
+isPackageNameConstituent :: Char -> Bool
+isPackageNameConstituent x = x == '-' || isAlphaNum x
+
+isModuleSegmentFirstChar :: Char -> Bool
+isModuleSegmentFirstChar x = isAlphaNum x && isUpper x
+
+isModuleSegmentConstituent :: Char -> Bool
+isModuleSegmentConstituent x =
+  x == '_' || x == '\'' || isAlphaNum x
diff --git a/src/Ormolu/Fixity/Printer.hs b/src/Ormolu/Fixity/Printer.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Fixity/Printer.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Printer for fixity overrides.
+module Ormolu.Fixity.Printer
+  ( printDotOrmolu,
+  )
+where
+
+import Data.Char qualified as Char
+import Data.List (intercalate)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Map.Strict qualified as Map
+import Data.Semigroup (sconcat)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.Lazy qualified as TL
+import Data.Text.Lazy.Builder (Builder)
+import Data.Text.Lazy.Builder qualified as B
+import Data.Text.Lazy.Builder.Int qualified as B
+import Data.Text.Lazy.Builder.RealFloat qualified as B
+import Distribution.ModuleName (ModuleName)
+import Distribution.ModuleName qualified as ModuleName
+import Distribution.Types.PackageName
+import Ormolu.Fixity
+
+-- | Print out a textual representation of an @.ormolu@ file.
+printDotOrmolu ::
+  FixityOverrides ->
+  ModuleReexports ->
+  Text
+printDotOrmolu
+  (FixityOverrides fixityOverrides)
+  (ModuleReexports moduleReexports) =
+    TL.toStrict . B.toLazyText $
+      (mconcat . fmap renderSingleFixityOverride . Map.toList) fixityOverrides
+        <> (mconcat . fmap renderSingleModuleReexport . Map.toList) moduleReexports
+
+renderSingleFixityOverride :: (OpName, FixityInfo) -> Builder
+renderSingleFixityOverride (OpName operator, FixityInfo {..}) =
+  mconcat
+    [ case fiDirection of
+        InfixL -> "infixl"
+        InfixR -> "infixr"
+        InfixN -> "infix",
+      " ",
+      renderPrecedence fiPrecedence,
+      " ",
+      if isTickedOperator operator
+        then "`" <> B.fromText operator <> "`"
+        else B.fromText operator,
+      "\n"
+    ]
+  where
+    isTickedOperator = maybe True (Char.isLetter . fst) . T.uncons
+
+renderSingleModuleReexport ::
+  (ModuleName, NonEmpty (Maybe PackageName, ModuleName)) ->
+  Builder
+renderSingleModuleReexport (exportingModule, exports) =
+  sconcat (renderSingle <$> exports)
+  where
+    renderSingle (mexportedPackage, exportedModule) =
+      mconcat
+        [ "module ",
+          renderModuleName exportingModule,
+          " exports ",
+          renderOptionalPackageName mexportedPackage,
+          renderModuleName exportedModule,
+          "\n"
+        ]
+    renderOptionalPackageName = \case
+      Nothing -> mempty
+      Just x -> "\"" <> B.fromString (unPackageName x) <> "\" "
+
+renderModuleName :: ModuleName -> Builder
+renderModuleName = B.fromString . intercalate "." . ModuleName.components
+
+-- | Render precedence using integer representation for whole numbers.
+renderPrecedence :: Double -> Builder
+renderPrecedence x =
+  let (n :: Int, fraction :: Double) = properFraction x
+      isWholeEnough = fraction < 0.0001
+   in if isWholeEnough
+        then B.decimal n
+        else B.realFloat x
diff --git a/src/Ormolu/Imports.hs b/src/Ormolu/Imports.hs
--- a/src/Ormolu/Imports.hs
+++ b/src/Ormolu/Imports.hs
@@ -1,6 +1,8 @@
+{-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ViewPatterns #-}
 
 -- | Manipulations on import lists.
 module Ormolu.Imports
@@ -11,17 +13,17 @@
 import Data.Bifunctor
 import Data.Char (isAlphaNum)
 import Data.Function (on)
-import Data.List (foldl', nubBy, sortBy, sortOn)
+import Data.List (nubBy, sortBy, sortOn)
 import Data.Map.Strict (Map)
-import qualified Data.Map.Strict as M
-import GHC.Data.FastString (FastString)
-import GHC.Hs.Extension
+import Data.Map.Strict qualified as M
+import Data.Ord (comparing)
+import GHC.Data.FastString
+import GHC.Hs
 import GHC.Hs.ImpExp as GHC
-import GHC.Types.Basic
 import GHC.Types.Name.Reader
+import GHC.Types.PkgQual
+import GHC.Types.SourceText
 import GHC.Types.SrcLoc
-import GHC.Unit.Module.Name
-import GHC.Unit.Types
 import Ormolu.Utils (notImplemented, showOutputable)
 
 -- | Sort and normalize imports.
@@ -32,11 +34,12 @@
     . M.fromListWith combineImports
     . fmap (\x -> (importId x, g x))
   where
+    g :: LImportDecl GhcPs -> LImportDecl GhcPs
     g (L l ImportDecl {..}) =
       L
         l
         ImportDecl
-          { ideclHiding = second (fmap normalizeLies) <$> ideclHiding,
+          { ideclImportList = second (fmap normalizeLies) <$> ideclImportList,
             ..
           }
 
@@ -50,7 +53,7 @@
   L
     lx
     ImportDecl
-      { ideclHiding = case (ideclHiding, GHC.ideclHiding y) of
+      { ideclImportList = case (ideclImportList, GHC.ideclImportList y) of
           (Just (hiding, L l' xs), Just (_, L _ ys)) ->
             Just (hiding, (L l' (normalizeLies (xs ++ ys))))
           _ -> Nothing,
@@ -62,39 +65,84 @@
 -- the same 'ImportId' they can be merged.
 data ImportId = ImportId
   { importIsPrelude :: Bool,
+    importPkgQual :: ImportPkgQual,
     importIdName :: ModuleName,
-    importPkgQual :: Maybe FastString,
     importSource :: IsBootInterface,
     importSafe :: Bool,
     importQualified :: Bool,
-    importImplicit :: Bool,
     importAs :: Maybe ModuleName,
-    importHiding :: Maybe Bool
+    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
+  | -- | The import is qualified by an external package name.
+    ImportPkgQual LexicalFastString
+  | -- | The import is qualified by the current package being built, using the
+    -- special @this@ package name.
+    ImportPkgQualThis
+  deriving stock (Eq, Ord)
+
+mkImportPkgQual :: RawPkgQual -> ImportPkgQual
+mkImportPkgQual = \case
+  NoRawPkgQual -> NoImportPkgQual
+  RawPkgQual (sl_fs -> fs)
+    | fs == mkFastString "this" -> ImportPkgQualThis
+    | otherwise -> ImportPkgQual (LexicalFastString fs)
+
+-- | 'ImportListInterpretation' does not have an 'Ord' instance.
+newtype ImportListInterpretationOrd = ImportListInterpretationOrd
+  { unImportListInterpretationOrd :: ImportListInterpretation
+  }
+  deriving stock (Eq)
+
+instance Ord ImportListInterpretationOrd where
+  compare = compare `on` toBool . unImportListInterpretationOrd
+    where
+      toBool Exactly = False
+      toBool EverythingBut = True
+
 -- | Obtain an 'ImportId' for a given import.
 importId :: LImportDecl GhcPs -> ImportId
 importId (L _ ImportDecl {..}) =
   ImportId
     { importIsPrelude = isPrelude,
       importIdName = moduleName,
-      importPkgQual = sl_fs <$> ideclPkgQual,
+      importPkgQual = mkImportPkgQual ideclPkgQual,
       importSource = ideclSource,
       importSafe = ideclSafe,
       importQualified = case ideclQualified of
         QualifiedPre -> True
         QualifiedPost -> True
         NotQualified -> False,
-      importImplicit = ideclImplicit,
       importAs = unLoc <$> ideclAs,
-      importHiding = fst <$> ideclHiding
+      importHiding = ImportListInterpretationOrd . fst <$> ideclImportList,
+      importLevel = importLevelOf ideclLevelSpec
     }
   where
     isPrelude = 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\/export items.
+-- | Normalize a collection of import items.
 normalizeLies :: [LIE GhcPs] -> [LIE GhcPs]
 normalizeLies = sortOn (getIewn . unLoc) . M.elems . foldl' combine M.empty
   where
@@ -109,80 +157,80 @@
           alter = \case
             Nothing -> Just . L new_l $
               case new of
-                IEThingWith NoExtField n wildcard g flbl ->
-                  IEThingWith NoExtField n wildcard (normalizeWNames g) flbl
+                IEThingWith x n wildcard g _ ->
+                  IEThingWith x n wildcard (normalizeWNames g) Nothing
                 other -> other
             Just old ->
               let f = \case
-                    IEVar NoExtField n -> IEVar NoExtField n
-                    IEThingAbs NoExtField _ -> new
-                    IEThingAll NoExtField n -> IEThingAll NoExtField n
-                    IEThingWith NoExtField n wildcard g flbl ->
+                    IEVar _ n _ -> IEVar Nothing n Nothing
+                    IEThingAbs _ _ _ -> new
+                    IEThingAll x n _ -> IEThingAll x n Nothing
+                    IEThingWith _ n wildcard g _ ->
                       case new of
-                        IEVar NoExtField _ ->
+                        IEVar _ _ _ ->
                           error "Ormolu.Imports broken presupposition"
-                        IEThingAbs NoExtField _ ->
-                          IEThingWith NoExtField n wildcard g flbl
-                        IEThingAll NoExtField n' ->
-                          IEThingAll NoExtField n'
-                        IEThingWith NoExtField n' wildcard' g' flbl' ->
+                        IEThingAbs x _ _ ->
+                          IEThingWith (x, noAnn) n wildcard g Nothing
+                        IEThingAll x n' _ ->
+                          IEThingAll x n' Nothing
+                        IEThingWith x n' wildcard' g' _ ->
                           let combinedWildcard =
                                 case (wildcard, wildcard') of
                                   (IEWildcard _, _) -> IEWildcard 0
                                   (_, IEWildcard _) -> IEWildcard 0
                                   _ -> NoIEWildcard
                            in IEThingWith
-                                NoExtField
+                                x
                                 n'
                                 combinedWildcard
                                 (normalizeWNames (g <> g'))
-                                flbl'
-                        IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
+                                Nothing
+                        IEModuleContents _ _ -> notImplemented "IEModuleContents"
                         IEGroup NoExtField _ _ -> notImplemented "IEGroup"
                         IEDoc NoExtField _ -> notImplemented "IEDoc"
                         IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
-                    IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
+                    IEModuleContents _ _ -> notImplemented "IEModuleContents"
                     IEGroup NoExtField _ _ -> notImplemented "IEGroup"
                     IEDoc NoExtField _ -> notImplemented "IEDoc"
                     IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
                in Just (f <$> old)
        in M.alter alter wname m
 
--- | A wrapper for @'IEWrappedName' 'RdrName'@ that allows us to define an
+-- | A wrapper for @'IEWrappedName' 'GhcPs'@ that allows us to define an
 -- 'Ord' instance for it.
-newtype IEWrappedNameOrd = IEWrappedNameOrd (IEWrappedName RdrName)
+newtype IEWrappedNameOrd = IEWrappedNameOrd (IEWrappedName GhcPs)
   deriving (Eq)
 
 instance Ord IEWrappedNameOrd where
   compare (IEWrappedNameOrd x) (IEWrappedNameOrd y) = compareIewn x y
 
--- | Project @'IEWrappedName' 'RdrName'@ from @'IE' 'GhcPs'@.
+-- | Project @'IEWrappedName' 'GhcPs'@ from @'IE' 'GhcPs'@.
 getIewn :: IE GhcPs -> IEWrappedNameOrd
 getIewn = \case
-  IEVar NoExtField x -> IEWrappedNameOrd (unLoc x)
-  IEThingAbs NoExtField x -> IEWrappedNameOrd (unLoc x)
-  IEThingAll NoExtField x -> IEWrappedNameOrd (unLoc x)
-  IEThingWith NoExtField x _ _ _ -> IEWrappedNameOrd (unLoc x)
-  IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
+  IEVar _ x _ -> IEWrappedNameOrd (unLoc x)
+  IEThingAbs _ x _ -> IEWrappedNameOrd (unLoc x)
+  IEThingAll _ x _ -> IEWrappedNameOrd (unLoc x)
+  IEThingWith _ x _ _ _ -> IEWrappedNameOrd (unLoc x)
+  IEModuleContents _ _ -> notImplemented "IEModuleContents"
   IEGroup NoExtField _ _ -> notImplemented "IEGroup"
   IEDoc NoExtField _ -> notImplemented "IEDoc"
   IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
 
 -- | Like 'compareIewn' for located wrapped names.
-compareLIewn :: LIEWrappedName RdrName -> LIEWrappedName RdrName -> Ordering
+compareLIewn :: LIEWrappedName GhcPs -> LIEWrappedName GhcPs -> Ordering
 compareLIewn = compareIewn `on` unLoc
 
--- | Compare two @'IEWrapppedName' 'RdrName'@ things.
-compareIewn :: IEWrappedName RdrName -> IEWrappedName RdrName -> Ordering
-compareIewn (IEName x) (IEName y) = unLoc x `compareRdrName` unLoc y
-compareIewn (IEName _) (IEPattern _) = LT
-compareIewn (IEName _) (IEType _) = LT
-compareIewn (IEPattern _) (IEName _) = GT
-compareIewn (IEPattern x) (IEPattern y) = unLoc x `compareRdrName` unLoc y
-compareIewn (IEPattern _) (IEType _) = LT
-compareIewn (IEType _) (IEName _) = GT
-compareIewn (IEType _) (IEPattern _) = GT
-compareIewn (IEType x) (IEType y) = unLoc x `compareRdrName` unLoc y
+-- | Compare two @'IEWrapppedName' 'GhcPs'@ things.
+compareIewn :: IEWrappedName GhcPs -> IEWrappedName GhcPs -> Ordering
+compareIewn = (comparing fst <> (compareRdrName `on` unLoc . snd)) `on` classify
+  where
+    classify :: IEWrappedName GhcPs -> (Int, LocatedN RdrName)
+    classify = \case
+      IEName _ x -> (0, x)
+      IEDefault _ x -> (1, x)
+      IEPattern _ x -> (2, x)
+      IEType _ x -> (3, x)
+      IEData _ x -> (4, x)
 
 compareRdrName :: RdrName -> RdrName -> Ordering
 compareRdrName x y =
diff --git a/src/Ormolu/Parser.hs b/src/Ormolu/Parser.hs
--- a/src/Ormolu/Parser.hs
+++ b/src/Ormolu/Parser.hs
@@ -1,5 +1,8 @@
+{-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
 -- | Parser for Haskell source code.
@@ -10,51 +13,66 @@
 where
 
 import Control.Exception
+import Control.Monad
+import Control.Monad.Except (ExceptT (..), runExceptT)
 import Control.Monad.IO.Class
-import qualified Data.List as L
-import qualified Data.List.NonEmpty as NE
-import Data.Ord (Down (Down))
-import qualified Data.Text as T
+import Data.Char (isSpace)
+import Data.Functor
+import Data.Generics hiding (orElse)
+import Data.List qualified as L
+import Data.List.NonEmpty qualified as NE
+import Data.Text (Text)
+import GHC.Builtin.Names (mAIN_NAME)
 import GHC.Data.Bag (bagToList)
-import qualified GHC.Data.FastString as GHC
-import qualified GHC.Data.StringBuffer as GHC
-import qualified GHC.Driver.CmdLine as GHC
+import GHC.Data.EnumSet qualified as EnumSet
+import GHC.Data.FastString qualified as GHC
+import GHC.Data.Maybe (orElse)
+import GHC.Data.StringBuffer (StringBuffer)
+import GHC.Driver.Config.Parser (initParserOpts, supportedLanguagePragmas)
+import GHC.Driver.Errors.Types qualified as GHC
 import GHC.Driver.Session as GHC
-import qualified GHC.Driver.Types as GHC
 import GHC.DynFlags (baseDynFlags)
+import GHC.Hs hiding (UnicodeSyntax)
 import GHC.LanguageExtensions.Type (Extension (..))
-import qualified GHC.Parser as GHC
-import qualified GHC.Parser.Header as GHC
-import qualified GHC.Parser.Lexer as GHC
+import GHC.Parser qualified as GHC
+import GHC.Parser.Annotation qualified as GHC
+import GHC.Parser.Header qualified as GHC
+import GHC.Parser.Lexer qualified as GHC
+import GHC.Types.Error qualified as GHC
+import GHC.Types.SourceError qualified as GHC
 import GHC.Types.SrcLoc
-import GHC.Unit.Module.Name
-import GHC.Utils.Error (Severity (..), errMsgSeverity, errMsgSpan)
-import qualified GHC.Utils.Panic as GHC
+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.Exception
-import Ormolu.Parser.Anns
+import Ormolu.Fixity hiding (packageFixityMap)
+import Ormolu.Fixity.Imports (applyModuleReexports, extractFixityImports)
+import Ormolu.Imports (normalizeImports)
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Result
-import Ormolu.Processing.Preprocess (preprocess)
-import Ormolu.Utils (incSpanLine, removeIndentation)
+import Ormolu.Processing.Common
+import Ormolu.Processing.Cpp (eraseCppLines)
+import Ormolu.Processing.Preprocess
+import Ormolu.Utils (incSpanLine, showOutputable, textToStringBuffer)
 
--- | Parse a complete module from string.
+-- | Parse a complete module from 'Text'.
 parseModule ::
-  MonadIO m =>
+  (MonadIO m) =>
   -- | Ormolu configuration
   Config RegionDeltas ->
+  -- | Package fixity map
+  PackageFixityMap ->
   -- | File name (only for source location annotations)
   FilePath ->
   -- | Input for parser
-  String ->
+  Text ->
   m
-    ( [GHC.Warn],
-      Either (SrcSpan, String) ParseResult
+    ( GHC.DriverMessages,
+      Either (SrcSpan, String) [SourceSnippet]
     )
-parseModule Config {..} path rawInput = liftIO $ do
-  let (literalPrefix, indentedInput, literalSuffix, extraComments) =
-        preprocess path rawInput cfgRegion
-      (input, indent) = removeIndentation indentedInput
+parseModule config@Config {..} packageFixityMap path rawInput = liftIO $ do
   -- It's important that 'setDefaultExts' is done before
   -- 'parsePragmasIntoDynFlags', because otherwise we might enable an
   -- extension that was explicitly disabled in the file.
@@ -63,34 +81,74 @@
           GHC.Opt_Haddock
           (setDefaultExts baseDynFlags)
       extraOpts = dynOptionToLocatedStr <$> cfgDynOptions
+      rawInputStringBuffer = textToStringBuffer (eraseCppLines rawInput)
+      beginningLoc =
+        mkSrcSpan
+          (mkSrcLoc (GHC.mkFastString path) 1 1)
+          (mkSrcLoc (GHC.mkFastString path) 1 1)
   (warnings, dynFlags) <-
-    parsePragmasIntoDynFlags baseFlags extraOpts path rawInput >>= \case
+    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)
+  fixityImports <-
+    parseImports dynFlags implicitPrelude path rawInputStringBuffer >>= \case
+      Right res ->
+        pure (applyModuleReexports cfgModuleReexports (extractFixityImports res))
       Left err ->
-        let loc =
-              mkSrcSpan
-                (mkSrcLoc (GHC.mkFastString path) 1 1)
-                (mkSrcLoc (GHC.mkFastString path) 1 1)
-         in throwIO (OrmoluParsingFailed loc err)
-  let useRecordDot =
-        "record-dot-preprocessor" == pgm_F dynFlags
-          || any
-            (("RecordDotPreprocessor" ==) . moduleNameString)
-            (pluginModNames dynFlags)
-      pStateErrors = \pstate ->
-        let errs = bagToList $ GHC.getErrorMessages pstate dynFlags
+        throwIO (OrmoluParsingFailed beginningLoc err)
+  let modFixityMap =
+        applyFixityOverrides
+          cfgFixityOverrides
+          (moduleFixityMap packageFixityMap fixityImports)
+  snippets <- runExceptT . forM (preprocess cppEnabled cfgRegion rawInput) $ \case
+    Right region ->
+      fmap ParsedSnippet . ExceptT $
+        parseModuleSnippet (config $> region) modFixityMap dynFlags path rawInput
+    Left raw -> pure $ RawSnippet raw
+  pure (warnings, snippets)
+
+parseModuleSnippet ::
+  (MonadIO m) =>
+  Config RegionDeltas ->
+  ModuleFixityMap ->
+  DynFlags ->
+  FilePath ->
+  Text ->
+  m (Either (SrcSpan, String) ParseResult)
+parseModuleSnippet Config {..} modFixityMap dynFlags path rawInput = liftIO $ do
+  let (input, indent) = removeIndentation . linesInRegion cfgRegion $ rawInput
+  let pStateErrors pstate =
+        let errs = bagToList . GHC.getMessages $ GHC.getPsErrorMessages pstate
             fixupErrSpan = incSpanLine (regionPrefixLength cfgRegion)
-         in case L.sortOn (Down . SeverityOrd . errMsgSeverity) errs of
+            rateSeverity = \case
+              SevError -> 1 :: Int
+              SevWarning -> 2
+              SevIgnore -> 3
+            showErr (errMsgDiagnostic -> err) = codeMsg <> msg
+              where
+                codeMsg = case diagnosticCode err of
+                  Just code -> "[" <> showOutputable code <> "] "
+                  Nothing -> ""
+                msg =
+                  showOutputable
+                    . formatBulleted
+                    . diagnosticMessage GHC.NoDiagnosticOpts
+                    $ err
+         in case L.sortOn (rateSeverity . errMsgSeverity) errs of
               [] -> Nothing
               err : _ ->
-                -- Show instance returns a short error message
-                Just (fixupErrSpan (errMsgSpan err), show err)
-      r = case runParser GHC.parseModule dynFlags path input of
+                Just (fixupErrSpan (errMsgSpan err), showErr err)
+      parser = case cfgSourceType of
+        ModuleSource -> GHC.parseModule
+        SignatureSource -> GHC.parseSignature
+      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 _ hsModule) ->
+        GHC.POk pstate (L _ (normalizeModule -> 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 "_"
@@ -98,28 +156,81 @@
             -- later stages; but we fail in those cases.
             Just err -> Left err
             Nothing ->
-              let (stackHeader, shebangs, pragmas, comments) =
-                    mkCommentStream input extraComments pstate
+              let (stackHeader, pragmas, comments) =
+                    mkCommentStream input hsModule
                in Right
                     ParseResult
                       { prParsedSource = hsModule,
-                        prAnns = mkAnns pstate,
+                        prSourceType = cfgSourceType,
                         prStackHeader = stackHeader,
-                        prShebangs = shebangs,
                         prPragmas = pragmas,
                         prCommentStream = comments,
-                        prUseRecordDot = useRecordDot,
                         prExtensions = GHC.extensionFlags dynFlags,
-                        prLiteralPrefix = T.pack literalPrefix,
-                        prLiteralSuffix = T.pack literalSuffix,
+                        prModuleFixityMap = modFixityMap,
                         prIndent = indent
                       }
-  return (warnings, r)
+  return r
 
+-- | Normalize a 'HsModule' by sorting its import\/export lists, dropping
+-- blank comments, etc.
+normalizeModule :: HsModule GhcPs -> HsModule GhcPs
+normalizeModule hsmod =
+  everywhere
+    ( mkT dropBlankTypeHaddocks
+        `extT` dropBlankDataDeclHaddocks
+        `extT` patchContext
+        `extT` patchExprContext
+    )
+    hsmod
+      { hsmodImports =
+          normalizeImports (hsmodImports hsmod),
+        hsmodDecls =
+          filter (not . isBlankDocD . unLoc) (hsmodDecls hsmod),
+        hsmodExt =
+          (hsmodExt hsmod)
+            { hsmodHaddockModHeader =
+                mfilter (not . isBlankDocString) (hsmodHaddockModHeader (hsmodExt hsmod))
+            },
+        hsmodExports =
+          (fmap . fmap) (filter (not . isBlankDocIE . unLoc)) (hsmodExports hsmod)
+      }
+  where
+    isBlankDocString = all isSpace . renderHsDocString . hsDocString . unLoc
+    isBlankDocD = \case
+      DocD _ s -> isBlankDocString $ docDeclDoc s
+      _ -> False
+    isBlankDocIE = \case
+      IEGroup _ _ s -> isBlankDocString s
+      IEDoc _ s -> isBlankDocString s
+      _ -> False
+    dropBlankTypeHaddocks = \case
+      L _ (HsDocTy _ ty s) :: LHsType GhcPs
+        | isBlankDocString s -> ty
+      a -> a
+    dropBlankDataDeclHaddocks = \case
+      ConDeclGADT {con_doc = Just s, ..} :: ConDecl GhcPs
+        | isBlankDocString s -> ConDeclGADT {con_doc = Nothing, ..}
+      ConDeclH98 {con_doc = Just s, ..} :: ConDecl GhcPs
+        | isBlankDocString s -> ConDeclH98 {con_doc = Nothing, ..}
+      a -> a
+
+    -- For constraint contexts (both in types and in expressions), normalize
+    -- parenthesis as decided in https://github.com/tweag/ormolu/issues/264.
+    patchContext :: LHsContext GhcPs -> LHsContext GhcPs
+    patchContext = fmap $ \case
+      [x@(L _ (HsParTy _ _))] -> [x]
+      [x@(L lx _)] -> [L lx (HsParTy noAnn x)]
+      xs -> xs
+    patchExprContext :: LHsExpr GhcPs -> LHsExpr GhcPs
+    patchExprContext = fmap $ \case
+      x@(HsQual _ (L _ [L _ HsPar {}]) _) -> x
+      HsQual l0 (L l1 [x@(L lx _)]) e -> HsQual l0 (L l1 [L lx (HsPar noAnn x)]) e
+      x -> x
+
 -- | Enable all language extensions that we think should be enabled by
 -- default for ease of use.
 setDefaultExts :: DynFlags -> DynFlags
-setDefaultExts flags = L.foldl' xopt_set flags autoExts
+setDefaultExts flags = L.foldl' xopt_set (lang_set flags (Just Haskell2010)) autoExts
   where
     autoExts = allExts L.\\ manualExts
     allExts = [minBound .. maxBound]
@@ -150,7 +261,12 @@
     -- decision of enabling 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
+    LinearTypes, -- steals the (%) type operator in some cases
+    OverloadedRecordDot, -- f.g parses differently
+    OverloadedRecordUpdate, -- qualified fields are not supported
+    OverloadedLabels, -- a#b is parsed differently
+    ExtendedLiterals, -- 1#Word32 is parsed differently
+    MultilineStrings -- """""" is parsed differently
   ]
 
 -- | Run a 'GHC.P' computation.
@@ -162,37 +278,20 @@
   -- | Module path
   FilePath ->
   -- | Module contents
-  String ->
+  Text ->
   -- | Parse result
   GHC.ParseResult a
 runParser parser flags filename input = GHC.unP parser parseState
   where
     location = mkRealSrcLoc (GHC.mkFastString filename) 1 1
-    buffer = GHC.stringToStringBuffer input
-    parseState = GHC.mkPState flags buffer location
-
--- | Wrap GHC's 'Severity' to add 'Ord' instance.
-newtype SeverityOrd = SeverityOrd Severity
-
-instance Eq SeverityOrd where
-  s1 == s2 = compare s1 s2 == EQ
-
-instance Ord SeverityOrd where
-  compare (SeverityOrd s1) (SeverityOrd s2) =
-    compare (f s1) (f s2)
-    where
-      f :: Severity -> Int
-      f SevOutput = 1
-      f SevFatal = 2
-      f SevInteractive = 3
-      f SevDump = 4
-      f SevInfo = 5
-      f SevWarning = 6
-      f SevError = 7
+    buffer = textToStringBuffer input
+    parseState = GHC.initParserState (initParserOpts flags) buffer location
 
 ----------------------------------------------------------------------------
 -- Helpers taken from HLint
 
+-- | Detect pragmas in the given input and return them as a collection of
+-- 'DynFlags'.
 parsePragmasIntoDynFlags ::
   -- | Pre-set 'DynFlags'
   DynFlags ->
@@ -201,22 +300,66 @@
   -- | File name (only for source location annotations)
   FilePath ->
   -- | Input for parser
-  String ->
-  IO (Either String ([GHC.Warn], DynFlags))
-parsePragmasIntoDynFlags flags extraOpts filepath str =
-  catchErrors $ do
-    let fileOpts = GHC.getOptions flags (GHC.stringToStringBuffer str) filepath
+  StringBuffer ->
+  IO (Either String (GHC.DriverMessages, DynFlags))
+parsePragmasIntoDynFlags flags extraOpts filepath input =
+  catchGhcErrors $ do
+    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 ->
         throwIO (OrmoluUnrecognizedOpts (unLoc <$> unrecognizedOpts))
     let flags'' = flags' `gopt_set` Opt_KeepRawTokenStream
     return $ Right (warnings, flags'')
+
+-- | Detect the collection of imports used in the given input.
+parseImports ::
+  -- | Pre-set 'DynFlags'
+  DynFlags ->
+  -- | Implicit Prelude?
+  Bool ->
+  -- | File name (only for source location annotations)
+  FilePath ->
+  -- | Input for the parser
+  StringBuffer ->
+  IO (Either String [LImportDecl GhcPs])
+parseImports flags implicitPrelude filepath input =
+  case GHC.unP GHC.parseHeader (GHC.initParserState popts input loc) of
+    GHC.PFailed pst ->
+      return $ Left (showOutputable (GHC.getPsErrorMessages pst))
+    GHC.POk pst rdr_module ->
+      return $
+        let (_warnings, errors) = GHC.getPsMessages pst
+         in if not (isEmptyMessages errors)
+              then Left (showOutputable (GHC.GhcPsMessage <$> errors))
+              else
+                let hsmod = unLoc rdr_module
+                    mmoduleName = hsmodName hsmod
+                    main_loc = srcLocSpan (mkSrcLoc (GHC.mkFastString filepath) 1 1)
+                    mod' = mmoduleName `orElse` L (GHC.noAnnSrcSpan main_loc) mAIN_NAME
+                    explicitImports = hsmodImports hsmod
+                    implicitImports =
+                      GHC.mkPrelImports (unLoc mod') main_loc implicitPrelude explicitImports
+                 in Right (explicitImports ++ implicitImports)
   where
-    catchErrors act =
-      GHC.handleGhcException
-        reportErr
-        (GHC.handleSourceError reportErr act)
+    popts = initParserOpts flags
+    loc = mkRealSrcLoc (GHC.mkFastString filepath) 1 1
+
+-- | Catch and report GHC errors.
+catchGhcErrors :: (ExceptionMonad m) => m (Either String a) -> m (Either String a)
+catchGhcErrors m =
+  GHC.handleGhcException
+    reportErr
+    (GHC.handleSourceError reportErr m)
+  where
     reportErr e = return $ Left (show e)
diff --git a/src/Ormolu/Parser/Anns.hs b/src/Ormolu/Parser/Anns.hs
deleted file mode 100644
--- a/src/Ormolu/Parser/Anns.hs
+++ /dev/null
@@ -1,42 +0,0 @@
--- | Ormolu-specific representation of GHC annotations.
-module Ormolu.Parser.Anns
-  ( Anns (..),
-    emptyAnns,
-    mkAnns,
-    lookupAnns,
-  )
-where
-
-import Data.Map.Strict (Map)
-import qualified Data.Map.Strict as M
-import GHC.Parser.Annotation
-import GHC.Parser.Lexer
-import GHC.Types.SrcLoc
-
--- | Ormolu-specific representation of GHC annotations.
-newtype Anns = Anns (Map RealSrcSpan [AnnKeywordId])
-  deriving (Eq)
-
--- | Empty 'Anns'.
-emptyAnns :: Anns
-emptyAnns = Anns M.empty
-
--- | Create 'Anns' from 'PState'.
-mkAnns ::
-  PState ->
-  Anns
-mkAnns pstate =
-  Anns $
-    M.fromListWith (++) (f <$> annotations pstate)
-  where
-    f ((rspn, kid), _) = (rspn, [kid])
-
--- | Lookup 'AnnKeywordId's corresponding to a given 'SrcSpan'.
-lookupAnns ::
-  -- | Span to lookup with
-  SrcSpan ->
-  -- | Collection of annotations
-  Anns ->
-  [AnnKeywordId]
-lookupAnns (RealSrcSpan rspn _) (Anns m) = M.findWithDefault [] rspn m
-lookupAnns (UnhelpfulSpan _) _ = []
diff --git a/src/Ormolu/Parser/CommentStream.hs b/src/Ormolu/Parser/CommentStream.hs
--- a/src/Ormolu/Parser/CommentStream.hs
+++ b/src/Ormolu/Parser/CommentStream.hs
@@ -1,15 +1,15 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ViewPatterns #-}
 
 -- | Functions for working with comment stream.
 module Ormolu.Parser.CommentStream
   ( -- * Comment stream
     CommentStream (..),
     mkCommentStream,
-    showCommentStream,
 
     -- * Comment
+    LComment,
     Comment (..),
     unComment,
     hasAtomsBefore,
@@ -17,78 +17,103 @@
   )
 where
 
+import Control.Monad ((<=<))
 import Data.Char (isSpace)
 import Data.Data (Data)
-import qualified Data.List as L
+import Data.Generics.Schemes
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
-import Data.Maybe (mapMaybe)
-import qualified GHC.Parser.Annotation as GHC
-import qualified GHC.Parser.Lexer as GHC
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Lazy qualified as M
+import Data.Maybe
+import Data.Set qualified as S
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Data.Strict qualified as Strict
+import GHC.Hs (HsModule)
+import GHC.Hs.Doc
+import GHC.Hs.Extension
+import GHC.Hs.ImpExp
+import GHC.Parser.Annotation (EpAnnComments (..), getLocA)
+import GHC.Parser.Annotation qualified as GHC
 import GHC.Types.SrcLoc
 import Ormolu.Parser.Pragma
-import Ormolu.Parser.Shebang
-import Ormolu.Processing.Common
-import Ormolu.Utils (onTheSameLine, showOutputable)
+import Ormolu.Utils (onTheSameLine)
 
 ----------------------------------------------------------------------------
 -- Comment stream
 
 -- | A stream of 'RealLocated' 'Comment's in ascending order with respect to
 -- beginning of corresponding spans.
-newtype CommentStream = CommentStream [RealLocated Comment]
+newtype CommentStream = CommentStream [LComment]
   deriving (Eq, Data, Semigroup, Monoid)
 
--- | Create 'CommentStream' from 'GHC.PState'. The pragmas and shebangs are
--- removed from the 'CommentStream'. Shebangs are only extracted from the
--- comments that come from the first argument.
+-- | Create 'CommentStream' from 'HsModule'. The pragmas are
+-- removed from the 'CommentStream'.
 mkCommentStream ::
   -- | Original input
-  String ->
-  -- | Extra comments to include
-  [RealLocated String] ->
-  -- | Parser state to use for comment extraction
-  GHC.PState ->
-  -- | Stack header, shebangs, pragmas, and comment stream
-  ( Maybe (RealLocated Comment),
-    [Shebang],
-    [([RealLocated Comment], Pragma)],
+  Text ->
+  -- | Module to use for comment extraction
+  HsModule GhcPs ->
+  -- | Stack header, pragmas, and comment stream
+  ( Maybe LComment,
+    [([LComment], Pragma)],
     CommentStream
   )
-mkCommentStream input extraComments pstate =
+mkCommentStream input hsModule =
   ( mstackHeader,
-    shebangs,
     pragmas,
     CommentStream comments
   )
   where
     (comments, pragmas) = extractPragmas input rawComments1
     (rawComments1, mstackHeader) = extractStackHeader rawComments0
-    rawComments0 =
-      L.sortOn (realSrcSpanStart . getRealSrcSpan) $
-        otherExtraComments
-          ++ mapMaybe (liftMaybe . fmap unAnnotationComment) (GHC.comment_q pstate)
-          ++ concatMap
-            (mapMaybe (liftMaybe . fmap unAnnotationComment) . snd)
-            (GHC.annotations_comments pstate)
-    (shebangs, otherExtraComments) = extractShebangs extraComments
 
--- | Pretty-print a 'CommentStream'.
-showCommentStream :: CommentStream -> String
-showCommentStream (CommentStream xs) =
-  unlines $
-    showComment <$> xs
-  where
-    showComment (L l str) = showOutputable l ++ " " ++ show str
+    -- We want to extract all comments except _valid_ Haddock comments
+    rawComments0 =
+      fmap (uncurry L)
+        . M.toAscList
+        . flip M.withoutKeys validHaddockCommentSpans
+        . M.fromList
+        . fmap (\(L l a) -> (l, a))
+        $ allComments
+      where
+        -- All comments, including valid and invalid Haddock comments
+        allComments =
+          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
+    only :: a -> Bool
+    only _ = True
 
 ----------------------------------------------------------------------------
 -- Comment
 
+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.
-data Comment = Comment Bool (NonEmpty String)
+data Comment = Comment Bool (NonEmpty Text)
   deriving (Eq, Show, Data)
 
 -- | Normalize comment string. Sometimes one multi-line comment is turned
@@ -96,44 +121,46 @@
 -- each line.
 mkComment ::
   -- | Lines of original input with their indices
-  [(Int, String)] ->
+  [(Int, Text)] ->
   -- | Raw comment string
-  RealLocated String ->
+  RealLocated Text ->
   -- | Remaining lines of original input and the constructed 'Comment'
-  ([(Int, String)], RealLocated Comment)
+  ([(Int, Text)], LComment)
 mkComment ls (L l s) = (ls', comment)
   where
     comment =
-      L l . Comment atomsBefore . removeConseqBlanks . fmap dropTrailing $
-        if "{-" `L.isPrefixOf` s
-          then case NE.nonEmpty (lines s) of
-            Nothing -> s :| []
-            Just (x :| xs) ->
-              let getIndent y =
-                    if all isSpace y || y == endDisabling
-                      then startIndent
-                      else length (takeWhile isSpace y)
-                  n = minimum (startIndent : fmap getIndent xs)
-                  removeIndent y =
-                    if y == endDisabling
-                      then y
-                      else drop n y
-               in x :| (removeIndent <$> xs)
-          else s :| []
+      L l . Comment atomsBefore . removeConseqBlanks . fmap T.stripEnd $
+        case NE.nonEmpty (T.lines s) of
+          Nothing -> s :| []
+          Just (x :| xs) ->
+            let getIndent y =
+                  if T.all isSpace y
+                    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)
     (atomsBefore, ls') =
       case dropWhile ((< commentLine) . fst) ls of
         [] -> (False, [])
         ((_, i) : ls'') ->
-          case take 2 (dropWhile isSpace i) of
+          case T.take 2 (T.stripStart i) of
             "--" -> (False, ls'')
             "{-" -> (False, ls'')
             _ -> (True, ls'')
-    dropTrailing = L.dropWhileEnd isSpace
-    startIndent = srcSpanStartCol l - 1
+    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
     commentLine = srcSpanStartLine l
 
 -- | Get a collection of lines from a 'Comment'.
-unComment :: Comment -> NonEmpty String
+unComment :: Comment -> NonEmpty Text
 unComment (Comment _ xs) = xs
 
 -- | Check whether the 'Comment' had some non-whitespace atoms in front of
@@ -143,7 +170,7 @@
 
 -- | Is this comment multiline-style?
 isMultilineComment :: Comment -> Bool
-isMultilineComment (Comment _ (x :| _)) = "{-" `L.isPrefixOf` x
+isMultilineComment (Comment _ (x :| _)) = "{-" `T.isPrefixOf` x
 
 ----------------------------------------------------------------------------
 -- Helpers
@@ -151,8 +178,8 @@
 -- | Detect and extract stack header if it is present.
 extractStackHeader ::
   -- | Comment stream to analyze
-  [RealLocated String] ->
-  ([RealLocated String], Maybe (RealLocated Comment))
+  [RealLocated Text] ->
+  ([RealLocated Text], Maybe LComment)
 extractStackHeader = \case
   [] -> ([], Nothing)
   (x : xs) ->
@@ -162,18 +189,18 @@
           else (x : xs, Nothing)
   where
     isStackHeader (Comment _ (x :| _)) =
-      "stack" `L.isPrefixOf` dropWhile isSpace (drop 2 x)
+      "stack" `T.isPrefixOf` T.stripStart (T.drop 2 x)
 
 -- | Extract pragmas and their associated comments.
 extractPragmas ::
   -- | Input
-  String ->
+  Text ->
   -- | Comment stream to analyze
-  [RealLocated String] ->
-  ([RealLocated Comment], [([RealLocated Comment], Pragma)])
+  [RealLocated Text] ->
+  ([LComment], [([LComment], Pragma)])
 extractPragmas input = go initialLs id id
   where
-    initialLs = zip [1 ..] (lines input)
+    initialLs = zip [1 ..] (T.lines input)
     go ls csSoFar pragmasSoFar = \case
       [] -> (csSoFar [], pragmasSoFar [])
       (x : xs) ->
@@ -189,34 +216,64 @@
                   (y : ys) ->
                     let (ls', y') = mkComment ls y
                      in if onTheSameLine
-                          (RealSrcSpan (getRealSrcSpan x) Nothing)
-                          (RealSrcSpan (getRealSrcSpan y) Nothing)
+                          (RealSrcSpan (getRealSrcSpan x) Strict.Nothing)
+                          (RealSrcSpan (getRealSrcSpan y) Strict.Nothing)
                           then go' ls' [y'] ys
                           else go' ls [] xs
 
--- | Get a 'String' from 'GHC.AnnotationComment'.
-unAnnotationComment :: GHC.AnnotationComment -> Maybe String
-unAnnotationComment = \case
-  GHC.AnnDocCommentNext _ -> Nothing -- @-- |@
-  GHC.AnnDocCommentPrev _ -> Nothing -- @-- ^@
-  GHC.AnnDocCommentNamed _ -> Nothing -- @-- $@
-  GHC.AnnDocSection _ _ -> Nothing -- @-- *@
-  GHC.AnnDocOptions s -> Just s
-  GHC.AnnLineComment s -> Just s
-  GHC.AnnBlockComment s -> Just s
-
-liftMaybe :: GenLocated l (Maybe a) -> Maybe (GenLocated l a)
-liftMaybe = \case
-  L _ Nothing -> Nothing
-  L l (Just a) -> Just (L l a)
+-- | Extract @'RealLocated' 'Text'@ from 'GHC.LEpaComment'.
+unAnnotationComment :: GHC.LEpaComment -> Maybe (RealLocated Text)
+unAnnotationComment (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)
+    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.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
 
 -- | Remove consecutive blank lines.
-removeConseqBlanks :: NonEmpty String -> NonEmpty String
-removeConseqBlanks (x :| xs) = x :| go (null x) id xs
+removeConseqBlanks :: NonEmpty Text -> NonEmpty Text
+removeConseqBlanks (x :| xs) = x :| go (T.null x) id xs
   where
     go seenBlank acc = \case
       [] -> acc []
       (y : ys) ->
-        if seenBlank && null y
+        if seenBlank && T.null y
           then go True acc ys
-          else go (null y) (acc . (y :)) ys
+          else go (T.null y) (acc . (y :)) ys
+
+-- | Escape characters that can turn a line into a Haddock.
+escapeHaddockTriggers :: Text -> Text
+escapeHaddockTriggers string
+  | Just (h, _) <- T.uncons string, h `elem` ("|^*$" :: [Char]) = T.cons '\\' string
+  | otherwise = string
diff --git a/src/Ormolu/Parser/Pragma.hs b/src/Ormolu/Parser/Pragma.hs
--- a/src/Ormolu/Parser/Pragma.hs
+++ b/src/Ormolu/Parser/Pragma.hs
@@ -8,75 +8,64 @@
   )
 where
 
-import Control.Monad
-import Data.Char (isSpace, toLower)
-import qualified Data.List as L
-import qualified GHC.Data.EnumSet as ES
-import GHC.Data.FastString (mkFastString, unpackFS)
-import GHC.Data.StringBuffer
-import qualified GHC.Parser.Lexer as L
+import Data.Char (isSpace)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import GHC.Data.FastString (bytesFS, mkFastString)
+import GHC.Driver.Config.Parser (initParserOpts)
+import GHC.DynFlags (baseDynFlags)
+import GHC.Parser.Lexer qualified as L
 import GHC.Types.SrcLoc
-import GHC.Unit.Module (stringToUnitId)
+import Ormolu.Utils (textToStringBuffer)
 
 -- | Ormolu's representation of pragmas.
 data Pragma
   = -- | Language pragma
-    PragmaLanguage [String]
+    PragmaLanguage [Text]
   | -- | GHC options pragma
-    PragmaOptionsGHC String
+    PragmaOptionsGHC Text
   | -- | Haddock options pragma
-    PragmaOptionsHaddock String
+    PragmaOptionsHaddock Text
   deriving (Show, Eq)
 
 -- | Extract a pragma from a comment if possible, or return 'Nothing'
 -- otherwise.
 parsePragma ::
   -- | Comment to try to parse
-  String ->
+  Text ->
   Maybe Pragma
 parsePragma input = do
-  inputNoPrefix <- L.stripPrefix "{-#" input
-  guard ("#-}" `L.isSuffixOf` input)
-  let contents = take (length inputNoPrefix - 3) inputNoPrefix
-      (pragmaName, cs) = (break isSpace . dropWhile isSpace) contents
-  case toLower <$> pragmaName of
+  contents <- T.stripSuffix "#-}" =<< T.stripPrefix "{-#" input
+  let (pragmaName, cs) = (T.break isSpace . T.dropWhile isSpace) contents
+  case T.toLower pragmaName of
     "language" -> PragmaLanguage <$> parseExtensions cs
-    "options_ghc" -> Just $ PragmaOptionsGHC (trimSpaces cs)
-    "options_haddock" -> Just $ PragmaOptionsHaddock (trimSpaces cs)
+    "options_ghc" -> Just $ PragmaOptionsGHC (T.strip cs)
+    "options_haddock" -> Just $ PragmaOptionsHaddock (T.strip cs)
     _ -> Nothing
-  where
-    trimSpaces :: String -> String
-    trimSpaces = L.dropWhileEnd isSpace . dropWhile isSpace
 
 -- | Assuming the input consists of a series of tokens from a language
 -- pragma, return the set of enabled extensions.
-parseExtensions :: String -> Maybe [String]
+parseExtensions :: Text -> Maybe [Text]
 parseExtensions str = tokenize str >>= go
   where
     go = \case
-      [L.ITconid ext] -> return [unpackFS ext]
-      (L.ITconid ext : L.ITcomma : xs) -> (unpackFS ext :) <$> go xs
+      [L.ITconid ext] -> return [fsToText ext]
+      (L.ITconid ext : L.ITcomma : xs) -> (fsToText ext :) <$> go xs
       _ -> Nothing
+    fsToText = T.decodeUtf8 . bytesFS
 
 -- | Tokenize a given input using GHC's lexer.
-tokenize :: String -> Maybe [L.Token]
+tokenize :: Text -> Maybe [L.Token]
 tokenize input =
   case L.unP pLexer parseState of
     L.PFailed {} -> Nothing
     L.POk _ x -> Just x
   where
     location = mkRealSrcLoc (mkFastString "") 1 1
-    buffer = stringToStringBuffer input
-    parseState = L.mkPStatePure parserFlags buffer location
-    parserFlags =
-      L.mkParserFlags'
-        ES.empty
-        ES.empty
-        (stringToUnitId "")
-        True
-        True
-        True
-        True
+    buffer = textToStringBuffer input
+    parseState = L.initParserState parserOpts buffer location
+    parserOpts = initParserOpts baseDynFlags
 
 -- | Haskell lexer.
 pLexer :: L.P [L.Token]
diff --git a/src/Ormolu/Parser/Result.hs b/src/Ormolu/Parser/Result.hs
--- a/src/Ormolu/Parser/Result.hs
+++ b/src/Ormolu/Parser/Result.hs
@@ -1,9 +1,7 @@
-{-# LANGUAGE RecordWildCards #-}
-
 -- | A type for result of parsing.
 module Ormolu.Parser.Result
-  ( ParseResult (..),
-    prettyPrintParseResult,
+  ( SourceSnippet (..),
+    ParseResult (..),
   )
 where
 
@@ -11,44 +9,30 @@
 import GHC.Data.EnumSet (EnumSet)
 import GHC.Hs
 import GHC.LanguageExtensions.Type
-import GHC.Types.SrcLoc
-import Ormolu.Parser.Anns
+import Ormolu.Config (SourceType)
+import Ormolu.Fixity (ModuleFixityMap)
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Pragma (Pragma)
-import Ormolu.Parser.Shebang (Shebang)
 
+-- | Either a 'ParseResult', or a raw snippet.
+data SourceSnippet = RawSnippet Text | ParsedSnippet ParseResult
+
 -- | A collection of data that represents a parsed module in Ormolu.
 data ParseResult = ParseResult
-  { -- | 'ParsedSource' from GHC
-    prParsedSource :: HsModule,
-    -- | Ormolu-specfic representation of annotations
-    prAnns :: Anns,
+  { -- | Parsed module or signature
+    prParsedSource :: HsModule GhcPs,
+    -- | Either regular module or signature file
+    prSourceType :: SourceType,
     -- | Stack header
-    prStackHeader :: Maybe (RealLocated Comment),
-    -- | Shebangs found in the input
-    prShebangs :: [Shebang],
+    prStackHeader :: Maybe LComment,
     -- | Pragmas and the associated comments
-    prPragmas :: [([RealLocated Comment], Pragma)],
+    prPragmas :: [([LComment], Pragma)],
     -- | Comment stream
     prCommentStream :: CommentStream,
-    -- | Whether or not record dot syntax is enabled
-    prUseRecordDot :: Bool,
     -- | Enabled extensions
     prExtensions :: EnumSet Extension,
-    -- | Literal prefix
-    prLiteralPrefix :: Text,
-    -- | Literal suffix
-    prLiteralSuffix :: Text,
+    -- | Fixity map for operators
+    prModuleFixityMap :: ModuleFixityMap,
     -- | Indentation level, can be non-zero in case of region formatting
     prIndent :: Int
   }
-
--- | Pretty-print a 'ParseResult'.
-prettyPrintParseResult :: ParseResult -> String
-prettyPrintParseResult ParseResult {..} =
-  unlines
-    [ "parse result:",
-      "  comment stream:",
-      showCommentStream prCommentStream
-      -- XXX extend as needed
-    ]
diff --git a/src/Ormolu/Parser/Shebang.hs b/src/Ormolu/Parser/Shebang.hs
deleted file mode 100644
--- a/src/Ormolu/Parser/Shebang.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-
--- | A module for dealing with shebangs.
-module Ormolu.Parser.Shebang
-  ( Shebang (..),
-    extractShebangs,
-    isShebang,
-  )
-where
-
-import Data.Data (Data)
-import qualified Data.List as L
-import GHC.Types.SrcLoc
-
--- | A wrapper for a shebang.
-newtype Shebang = Shebang (RealLocated String)
-  deriving (Eq, Data)
-
--- | Extract shebangs from the beginning of a comment stream.
-extractShebangs :: [RealLocated String] -> ([Shebang], [RealLocated String])
-extractShebangs comments = (Shebang <$> shebangs, rest)
-  where
-    (shebangs, rest) = span (isShebang . unLoc) comments
-
--- | Return 'True' if given 'String' is a shebang.
-isShebang :: String -> Bool
-isShebang str = "#!" `L.isPrefixOf` str
diff --git a/src/Ormolu/Printer.hs b/src/Ormolu/Printer.hs
--- a/src/Ormolu/Printer.hs
+++ b/src/Ormolu/Printer.hs
@@ -1,38 +1,43 @@
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Pretty-printer for Haskell AST.
 module Ormolu.Printer
-  ( printModule,
+  ( printSnippets,
   )
 where
 
 import Data.Text (Text)
+import Data.Text qualified as T
 import Ormolu.Parser.Result
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Module
 import Ormolu.Printer.SpanStream
-import Ormolu.Processing.Postprocess (postprocess)
+import Ormolu.Processing.Common
 
--- | Render a module.
-printModule ::
+-- | Render several source snippets.
+printSnippets ::
+  -- | Whether to print out debug information during printing
+  Bool ->
   -- | Result of parsing
-  ParseResult ->
+  [SourceSnippet] ->
   -- | Resulting rendition
   Text
-printModule ParseResult {..} =
-  prLiteralPrefix <> region <> prLiteralSuffix
+printSnippets debug = T.concat . fmap printSnippet
   where
-    region =
-      postprocess prIndent $
-        runR
-          ( p_hsModule
-              prStackHeader
-              prShebangs
-              prPragmas
-              prParsedSource
-          )
-          (mkSpanStream prParsedSource)
-          prCommentStream
-          prAnns
-          prUseRecordDot
-          prExtensions
+    printSnippet = \case
+      ParsedSnippet ParseResult {..} ->
+        reindent prIndent $
+          runR
+            ( p_hsModule
+                prStackHeader
+                prPragmas
+                prParsedSource
+            )
+            (mkSpanStream prParsedSource)
+            prCommentStream
+            prSourceType
+            prExtensions
+            prModuleFixityMap
+            debug
+      RawSnippet r -> r
diff --git a/src/Ormolu/Printer/Combinators.hs b/src/Ormolu/Printer/Combinators.hs
--- a/src/Ormolu/Printer/Combinators.hs
+++ b/src/Ormolu/Printer/Combinators.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
@@ -10,8 +9,9 @@
   ( -- * The 'R' monad
     R,
     runR,
-    getAnns,
     getEnclosingSpan,
+    getEnclosingSpanWhere,
+    getEnclosingComments,
     isExtensionEnabled,
 
     -- * Combinators
@@ -21,12 +21,15 @@
     atom,
     space,
     newline,
+    newlineLiteral,
     inci,
     inciIf,
-    inciHalf,
+    askSourceType,
+    askModuleFixityMap,
+    askDebug,
     located,
+    encloseLocated,
     located',
-    realLocated,
     switchLayout,
     Layout (..),
     vlayout,
@@ -37,6 +40,7 @@
     -- ** Formatting lists
     sep,
     sepSemi,
+    sepSemi',
     canUseBraces,
     useBraces,
     dontUseBraces,
@@ -47,7 +51,6 @@
     backticks,
     banana,
     braces,
-    recordDotBraces,
     brackets,
     parens,
     parensHash,
@@ -65,12 +68,18 @@
     HaddockStyle (..),
     setSpanMark,
     getSpanMark,
+
+    -- ** Placement
+    Placement (..),
+    placeHanging,
   )
 where
 
 import Control.Monad
 import Data.List (intersperse)
 import Data.Text (Text)
+import GHC.Data.Strict qualified as Strict
+import GHC.Parser.Annotation
 import GHC.Types.SrcLoc
 import Ormolu.Printer.Comments
 import Ormolu.Printer.Internal
@@ -87,39 +96,50 @@
   R ()
 inciIf b m = if b then inci m else m
 
--- | Enter a 'Located' entity. This combinator handles outputting comments
+-- | Enter a 'GenLocated' entity. This combinator handles outputting comments
 -- and sets 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.
 located ::
+  (HasLoc l) =>
   -- | Thing to enter
-  Located a ->
+  GenLocated l a ->
   -- | How to render inner value
   (a -> R ()) ->
   R ()
-located (L (UnhelpfulSpan _) a) f = f a
-located (L (RealSrcSpan l _) a) f = realLocated (L l a) f
+located (L l' a) f = case locA l' of
+  UnhelpfulSpan _ -> f a
+  RealSrcSpan l _ -> do
+    spitPrecedingComments l
+    withEnclosingSpan l $
+      switchLayout [RealSrcSpan l Strict.Nothing] (f a)
+    spitFollowingComments l
 
--- | See 'located'
-realLocated ::
-  -- | Thing to enter
-  RealLocated a ->
-  -- | How to render inner value
-  (a -> R ()) ->
+-- | 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 ()) ->
   R ()
-realLocated (L l a) f = do
-  spitPrecedingComments l
-  withEnclosingSpan l $
-    switchLayout [RealSrcSpan l Nothing] (f a)
-  spitFollowingComments l
+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)
 
 -- | A version of 'located' with arguments flipped.
 located' ::
+  (HasLoc l) =>
   -- | How to render inner value
   (a -> R ()) ->
   -- | Thing to enter
-  Located a ->
+  GenLocated l a ->
   R ()
 located' = flip located
 
@@ -189,7 +209,25 @@
   -- | Elements to render
   [a] ->
   R ()
-sepSemi f xs = vlayout singleLine multiLine
+sepSemi = sepSemi' False
+
+-- | A version of 'sepSemi' that allows 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
@@ -205,7 +243,10 @@
               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
@@ -233,23 +274,6 @@
 braces :: BracketStyle -> R () -> R ()
 braces = brackets_ False "{" "}"
 
--- | Surround record update fields which use RecordDot plugin entity by
--- curly braces @{@ and @}@.
---
--- @since 0.1.3.1
-recordDotBraces :: R () -> R ()
-recordDotBraces m = sitcc (vlayout singleLine multiLine)
-  where
-    singleLine = do
-      txt "{"
-      m
-      txt "}"
-    multiLine = do
-      txt "{"
-      sitcc m
-      newline
-      txt "}"
-
 -- | Surround given entity by square brackets @[@ and @]@.
 brackets :: BracketStyle -> R () -> R ()
 brackets = brackets_ False "[" "]"
@@ -326,3 +350,32 @@
 -- | Print @=@. Do not use @'txt' "="@.
 equals :: R ()
 equals = interferingTxt "="
+
+----------------------------------------------------------------------------
+-- Placement
+
+-- | Expression placement. This marks the places where expressions that
+-- implement handing forms may use them.
+data Placement
+  = -- | Multi-line layout should cause
+    -- insertion of a newline and indentation
+    -- bump
+    Normal
+  | -- | Expressions that have hanging form
+    -- should use it and avoid bumping one level
+    -- of indentation
+    Hanging
+  deriving (Eq, Show)
+
+-- | Place a thing that may have a hanging form. This function handles how
+-- to separate it from preceding expressions and whether to bump indentation
+-- depending on what sort of expression we have.
+placeHanging :: Placement -> R () -> R ()
+placeHanging placement m =
+  case placement of
+    Hanging -> do
+      space
+      m
+    Normal -> do
+      breakpoint
+      inci m
diff --git a/src/Ormolu/Printer/Comments.hs b/src/Ormolu/Printer/Comments.hs
--- a/src/Ormolu/Printer/Comments.hs
+++ b/src/Ormolu/Printer/Comments.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 -- | Helpers for formatting of comments. This is low-level code, use
@@ -13,9 +12,8 @@
 where
 
 import Control.Monad
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe (listToMaybe)
-import qualified Data.Text as T
 import GHC.Types.SrcLoc
 import Ormolu.Parser.CommentStream
 import Ormolu.Printer.Internal
@@ -29,8 +27,8 @@
   RealSrcSpan ->
   R ()
 spitPrecedingComments ref = do
-  gotSome <- handleCommentSeries (spitPrecedingComment ref)
-  when gotSome $ do
+  comments <- handleCommentSeries (spitPrecedingComment ref)
+  when (not $ null comments) $ do
     lastMark <- getSpanMark
     -- Insert a blank line between the preceding comments and the thing
     -- after them if there was a blank line in the input.
@@ -60,8 +58,8 @@
 spitPrecedingComment ::
   -- | Span of the element to attach comments to
   RealSrcSpan ->
-  -- | Are we done?
-  R Bool
+  -- | The comment that was output, if any
+  R (Maybe LComment)
 spitPrecedingComment ref = do
   mlastMark <- getSpanMark
   let p (L l _) = realSrcSpanEnd l <= realSrcSpanStart ref
@@ -83,14 +81,14 @@
 spitFollowingComment ::
   -- | AST element to attach comments to
   RealSrcSpan ->
-  -- | Are we done?
-  R Bool
+  -- | 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 <- getEnclosingSpan (/= ref)
+  meSpn <- getEnclosingSpanWhere (/= ref)
   withPoppedComment (commentFollowsElt ref mnSpn meSpn mlastMark) $ \l comment ->
     if theSameLinePost l ref
       then
@@ -104,8 +102,8 @@
 
 -- | Output a single remaining comment from the comment stream.
 spitRemainingComment ::
-  -- | Are we done?
-  R Bool
+  -- | The comment that was output, if any
+  R (Maybe LComment)
 spitRemainingComment = do
   mlastMark <- getSpanMark
   withPoppedComment (const True) $ \l comment -> do
@@ -118,33 +116,33 @@
 
 -- | Output series of comments.
 handleCommentSeries ::
-  -- | Given location of previous comment, output the next comment
-  -- returning 'True' if we're done
-  R Bool ->
-  -- | Whether we printed any comments
-  R Bool
-handleCommentSeries f = go False
+  -- | Output and return the next comment, if any
+  R (Maybe LComment) ->
+  -- | The comments outputted
+  R [LComment]
+handleCommentSeries f = go
   where
-    go gotSome = do
-      done <- f
-      if done
-        then return gotSome
-        else go True
+    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
-  (RealLocated Comment -> Bool) ->
+  (LComment -> Bool) ->
   -- | Printing function
   (RealSrcSpan -> Comment -> R ()) ->
   -- | Are we done?
-  R Bool
+  R (Maybe LComment)
 withPoppedComment p f = do
   r <- popComment p
   case r of
-    Nothing -> return True
-    Just (L l comment) -> False <$ f l comment
+    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.
@@ -192,7 +190,7 @@
   -- | Location of last comment in the series
   Maybe SpanMark ->
   -- | Comment to test
-  RealLocated Comment ->
+  LComment ->
   Bool
 commentFollowsElt ref mnSpn meSpn mlastMark (L l comment) =
   -- A comment follows a AST element if all 4 conditions are satisfied:
@@ -264,7 +262,7 @@
   sitcc
     . sequence_
     . NE.intersperse newline
-    . fmap (txt . T.pack)
+    . fmap txt
     . unComment
     $ comment
   setSpanMark (CommentSpan spn)
@@ -280,7 +278,7 @@
   wrapper
     . sequence_
     . NE.toList
-    . fmap (registerPendingCommentLine position . T.pack)
+    . fmap (registerPendingCommentLine position)
     . unComment
     $ comment
   setSpanMark (CommentSpan spn)
diff --git a/src/Ormolu/Printer/Internal.hs b/src/Ormolu/Printer/Internal.hs
--- a/src/Ormolu/Printer/Internal.hs
+++ b/src/Ormolu/Printer/Internal.hs
@@ -1,7 +1,6 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
 
 -- | In most cases import "Ormolu.Printer.Combinators" instead, these
 -- functions are the low-level building blocks and should not be used on
@@ -18,9 +17,11 @@
     atom,
     space,
     newline,
-    useRecordDot,
+    newlineLiteral,
+    askSourceType,
+    askModuleFixityMap,
+    askDebug,
     inci,
-    inciHalf,
     sitcc,
     Layout (..),
     enterLayout,
@@ -38,7 +39,9 @@
     trimSpanStream,
     nextEltSpan,
     popComment,
+    getEnclosingComments,
     getEnclosingSpan,
+    getEnclosingSpanWhere,
     withEnclosingSpan,
     thisLineSpans,
 
@@ -49,30 +52,32 @@
     setSpanMark,
     getSpanMark,
 
-    -- * Annotations
-    getAnns,
-
     -- * Extensions
     isExtensionEnabled,
   )
 where
 
+import Control.Monad
 import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Data.Bool (bool)
+import Data.Choice (Choice)
+import Data.Choice qualified as Choice
 import Data.Coerce
+import Data.Functor ((<&>))
+import Data.List (find)
 import Data.Maybe (listToMaybe)
 import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Lazy as TL
+import Data.Text qualified as T
+import Data.Text.Lazy qualified as TL
 import Data.Text.Lazy.Builder
 import GHC.Data.EnumSet (EnumSet)
-import qualified GHC.Data.EnumSet as EnumSet
+import GHC.Data.EnumSet qualified as EnumSet
 import GHC.LanguageExtensions.Type
-import GHC.Parser.Annotation
 import GHC.Types.SrcLoc
 import GHC.Utils.Outputable (Outputable)
-import Ormolu.Parser.Anns
+import Ormolu.Config (SourceType (..))
+import Ormolu.Fixity (ModuleFixityMap)
 import Ormolu.Parser.CommentStream
 import Ormolu.Printer.SpanStream
 import Ormolu.Utils (showOutputable)
@@ -95,14 +100,16 @@
     rcLayout :: Layout,
     -- | Spans of enclosing elements of AST
     rcEnclosingSpans :: [RealSrcSpan],
-    -- | Collection of annotations
-    rcAnns :: Anns,
     -- | Whether the last expression in the layout can use braces
     rcCanUseBraces :: Bool,
-    -- | Whether the source could have used the record dot preprocessor
-    rcUseRecDot :: Bool,
     -- | Enabled extensions
-    rcExtensions :: EnumSet Extension
+    rcExtensions :: EnumSet Extension,
+    -- | Whether the source is a signature or a regular module
+    rcSourceType :: SourceType,
+    -- | Module fixity map
+    rcModuleFixityMap :: ModuleFixityMap,
+    -- | Whether to print out debug information during printing
+    rcDebug :: !Bool
   }
 
 -- | State context of 'R'.
@@ -166,15 +173,16 @@
   SpanStream ->
   -- | Comment stream
   CommentStream ->
-  -- | Annotations
-  Anns ->
-  -- | Use Record Dot Syntax
-  Bool ->
+  -- | Whether the source is a signature or a regular module
+  SourceType ->
   -- | Enabled extensions
   EnumSet Extension ->
+  -- | Module fixity map
+  ModuleFixityMap ->
   -- | Resulting rendition
+  Bool ->
   Text
-runR (R m) sstream cstream anns recDot extensions =
+runR (R m) sstream cstream sourceType extensions moduleFixityMap debug =
   TL.toStrict . toLazyText . scBuilder $ execState (runReaderT m rc) sc
   where
     rc =
@@ -182,10 +190,11 @@
         { rcIndent = 0,
           rcLayout = MultiLine,
           rcEnclosingSpans = [],
-          rcAnns = anns,
           rcCanUseBraces = False,
-          rcUseRecDot = recDot,
-          rcExtensions = extensions
+          rcExtensions = extensions,
+          rcSourceType = sourceType,
+          rcModuleFixityMap = moduleFixityMap,
+          rcDebug = debug
         }
     sc =
       SC
@@ -245,7 +254,7 @@
 -- literals and similar. Everything that doesn't have inner structure but
 -- does have an 'Outputable' instance.
 atom ::
-  Outputable a =>
+  (Outputable a) =>
   a ->
   R ()
 atom = spit Atom . T.pack . showOutputable
@@ -275,7 +284,7 @@
   R $ do
     i <- asks rcIndent
     c <- gets scColumn
-    closestEnclosing <- listToMaybe <$> asks rcEnclosingSpans
+    closestEnclosing <- asks (listToMaybe . rcEnclosingSpans)
     let indentedTxt = spaces <> text
         spaces = T.replicate spacesN " "
         spacesN =
@@ -378,10 +387,32 @@
             _ -> AfterNewline
         }
 
--- | Return 'True' if we should print record dot syntax.
-useRecordDot :: R Bool
-useRecordDot = R (asks rcUseRecDot)
+-- | Insert a newline literal without modifying the internal state of the
+-- parser. This is to be used exceptionally, 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)
+
+-- | Retrieve the module fixity map.
+askModuleFixityMap :: R ModuleFixityMap
+askModuleFixityMap = R (asks rcModuleFixityMap)
+
+-- | Retrieve whether we should print out certain debug information while
+-- printing.
+askDebug :: R (Choice "debug")
+askDebug = R (asks (Choice.fromBool . rcDebug))
+
 inciBy :: Int -> R () -> R ()
 inciBy step (R m) = R (local modRC m)
   where
@@ -398,11 +429,6 @@
 inci :: R () -> R ()
 inci = inciBy indentStep
 
--- | In rare cases, we have to indent by a positive amount smaller
--- than 'indentStep'.
-inciHalf :: R () -> R ()
-inciHalf = inciBy $ (indentStep `div` 2) `max` 1
-
 -- | Set indentation level for the inner computation equal to current
 -- column. This makes sure that the entire inner block is uniformly
 -- \"shifted\" to the right.
@@ -483,31 +509,37 @@
 -- | Pop a 'Comment' from the 'CommentStream' if given predicate is
 -- satisfied and there are comments in the stream.
 popComment ::
-  (RealLocated Comment -> Bool) ->
-  R (Maybe (RealLocated Comment))
+  (LComment -> Bool) ->
+  R (Maybe LComment)
 popComment f = R $ do
   CommentStream cstream <- gets scCommentStream
   case cstream of
-    [] -> return Nothing
-    (x : xs) ->
-      if f x
-        then
-          Just x
-            <$ modify
-              ( \sc ->
-                  sc
-                    { scCommentStream = CommentStream xs
-                    }
-              )
-        else return Nothing
+    (x : xs) | f x -> do
+      modify $ \sc -> sc {scCommentStream = CommentStream xs}
+      return $ Just x
+    _ -> return Nothing
 
+-- | 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 immediately enclosing 'RealSrcSpan'.
+getEnclosingSpan :: R (Maybe RealSrcSpan)
+getEnclosingSpan = getEnclosingSpanWhere (const True)
+
 -- | Get the first enclosing 'RealSrcSpan' that satisfies given predicate.
-getEnclosingSpan ::
+getEnclosingSpanWhere ::
   -- | Predicate to use
   (RealSrcSpan -> Bool) ->
   R (Maybe RealSrcSpan)
-getEnclosingSpan f =
-  listToMaybe . filter f <$> R (asks rcEnclosingSpans)
+getEnclosingSpanWhere f =
+  find f <$> R (asks rcEnclosingSpans)
 
 -- | Set 'RealSrcSpan' of enclosing span for the given computation.
 withEnclosingSpan :: RealSrcSpan -> R () -> R ()
@@ -565,15 +597,6 @@
 -- | Get span of last output comment.
 getSpanMark :: R (Maybe SpanMark)
 getSpanMark = R (gets scSpanMark)
-
-----------------------------------------------------------------------------
--- Annotations
-
--- | For a given span return 'AnnKeywordId's associated with it.
-getAnns ::
-  SrcSpan ->
-  R [AnnKeywordId]
-getAnns spn = lookupAnns spn <$> R (asks rcAnns)
 
 ----------------------------------------------------------------------------
 -- Helpers for braces
diff --git a/src/Ormolu/Printer/Meat/Common.hs b/src/Ormolu/Printer/Meat/Common.hs
--- a/src/Ormolu/Printer/Meat/Common.hs
+++ b/src/Ormolu/Printer/Meat/Common.hs
@@ -1,5 +1,7 @@
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ViewPatterns #-}
 
 -- | Rendering of commonly useful bits.
 module Ormolu.Printer.Meat.Common
@@ -7,27 +9,34 @@
     p_hsmodName,
     p_ieWrappedName,
     p_rdrName,
-    doesNotNeedExtraParens,
     p_qualName,
     p_infixDefHelper,
-    p_hsDocString,
+    p_hsDoc,
     p_hsDocName,
     p_sourceText,
+    p_namespaceSpec,
+    p_hsMultAnn,
   )
 where
 
 import Control.Monad
-import Data.List (isPrefixOf)
-import qualified Data.Text as T
+import Data.Choice (Choice)
+import Data.Choice qualified as Choice
+import Data.Text qualified as T
+import GHC.Data.FastString
+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.Basic
-import GHC.Types.Name (nameStableString)
-import GHC.Types.Name.Occurrence (OccName (..))
+import GHC.Types.Name.Occurrence (OccName (..), occNameString)
 import GHC.Types.Name.Reader
+import GHC.Types.SourceText
 import GHC.Types.SrcLoc
-import GHC.Unit.Module.Name
+import Language.Haskell.Syntax.Module.Name
+import Ormolu.Config (SourceType (..))
 import Ormolu.Printer.Combinators
 import Ormolu.Utils
 
@@ -38,79 +47,79 @@
   | -- | Top-level declarations
     Free
 
+-- | Outputs the name of the module-like entity, preceded by the correct prefix ("module" or "signature").
 p_hsmodName :: ModuleName -> R ()
 p_hsmodName mname = do
-  txt "module"
+  sourceType <- askSourceType
+  txt $ case sourceType of
+    ModuleSource -> "module"
+    SignatureSource -> "signature"
   space
   atom mname
 
-p_ieWrappedName :: IEWrappedName RdrName -> R ()
+p_ieWrappedName :: IEWrappedName GhcPs -> R ()
 p_ieWrappedName = \case
-  IEName x -> p_rdrName x
-  IEPattern x -> do
+  IEName _ x -> p_rdrName x
+  IEDefault _ x -> do
+    txt "default"
+    space
+    p_rdrName x
+  IEPattern _ x -> do
     txt "pattern"
     space
     p_rdrName x
-  IEType x -> do
+  IEType _ x -> do
     txt "type"
     space
     p_rdrName x
+  IEData _ x -> do
+    txt "data"
+    space
+    p_rdrName x
 
--- | Render a @'Located' 'RdrName'@.
-p_rdrName :: Located RdrName -> R ()
-p_rdrName l@(L spn _) = located l $ \x -> do
-  ids <- getAnns spn
-  let backticksWrapper =
-        if AnnBackquote `elem` ids
-          then backticks
-          else id
-      parensWrapper =
-        if AnnOpenP `elem` ids
-          then parens N
-          else id
-      singleQuoteWrapper =
-        if AnnSimpleQuote `elem` ids
-          then \y -> do
-            txt "'"
-            y
-          else id
-      m =
-        case x of
-          Unqual occName ->
-            atom occName
-          Qual mname occName ->
-            p_qualName mname occName
-          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
-            -- specified in the source code, e.g. @Prelude.map@.
-            --
-            -- My current understanding is that the provided module name
-            -- serves no purpose for us and can be safely ignored.
-            atom occName
-          Exact name ->
-            atom name
-      m' = backticksWrapper (singleQuoteWrapper m)
-  if doesNotNeedExtraParens x
-    then m'
-    else parensWrapper m'
+-- | Render a @'LocatedN' 'RdrName'@.
+p_rdrName :: LocatedN RdrName -> R ()
+p_rdrName l = located l $ \x -> do
+  unboxedSums <- isExtensionEnabled UnboxedSums
+  let wrapper EpAnn {anns} = case anns of
+        NameAnnQuote {nann_quoted} -> tickPrefix . wrapper nann_quoted
+        NameAnn {nann_adornment = NameParens {}} ->
+          parens N . handleUnboxedSumsAndHashInteraction
+        NameAnn {nann_adornment = NameBackquotes {}} -> backticks
+        -- whether the `->` identifier is parenthesized
+        NameAnnRArrow {nann_mopen = Just _} -> parens N
+        -- special case for unboxed unit tuples
+        NameAnnOnly {nann_adornment = NameParensHash {}} -> const $ txt "(# #)"
+        _ -> id
 
--- | Whether given name should not have parentheses around it. This is used
--- to detect e.g. tuples for which annotations will indicate parentheses,
--- but the parentheses are already part of the symbol, so no extra layer of
--- parentheses should be added. It also detects the [] literal.
-doesNotNeedExtraParens :: RdrName -> Bool
-doesNotNeedExtraParens = \case
-  Exact name ->
-    let s = nameStableString name
-     in -- I'm not sure this "stable string" is stable enough, but it looks
-        -- like this is the most robust way to tell if we're looking at
-        -- exactly this piece of built-in syntax.
-        ("$ghc-prim$GHC.Tuple$" `isPrefixOf` s)
-          || ("$ghc-prim$GHC.Types$[]" `isPrefixOf` s)
-  _ -> False
+      -- 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 with a `#`.
+          Unqual (occNameString -> '#' : _) <- x =
+            \y -> space *> y <* space
+        | otherwise = id
 
+  wrapper (getLoc l) $ case x of
+    Unqual occName ->
+      atom occName
+    Qual mname occName ->
+      p_qualName mname occName
+    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
+      -- specified in the source code, e.g. @Prelude.map@.
+      --
+      -- My current understanding is that the provided module name
+      -- serves no purpose for us and can be safely ignored.
+      atom occName
+    Exact name ->
+      atom name
+  where
+    tickPrefix y = txt "'" *> y
+
 p_qualName :: ModuleName -> OccName -> R ()
 p_qualName mname occName = do
   atom mname
@@ -152,15 +161,15 @@
         inciIf indentArgs $ sitcc (sep breakpoint sitcc args)
 
 -- | Print a Haddock.
-p_hsDocString ::
+p_hsDoc ::
   -- | Haddock style
   HaddockStyle ->
   -- | Finish the doc string with a newline
-  Bool ->
-  -- | The doc string to render
-  LHsDocString ->
+  Choice "endNewline" ->
+  -- | The 'LHsDoc' to render
+  LHsDoc GhcPs ->
   R ()
-p_hsDocString hstyle needsNewline (L l str) = do
+p_hsDoc hstyle needsNewline (L l str) = do
   let isCommentSpan = \case
         HaddockSpan _ _ -> True
         CommentSpan _ -> True
@@ -168,7 +177,8 @@
   goesAfterComment <- maybe False isCommentSpan <$> getSpanMark
   -- Make sure the Haddock is separated by a newline from other comments.
   when goesAfterComment newline
-  forM_ (zip (splitDocString str) (True : repeat False)) $ \(x, isFirst) -> do
+  let docStringLines = splitDocString $ hsDocString str
+  forM_ (zip docStringLines (True : repeat False)) $ \(x, isFirst) -> do
     if isFirst
       then case hstyle of
         Pipe -> txt "-- |"
@@ -178,13 +188,13 @@
       else newline >> txt "--"
     space
     unless (T.null x) (txt x)
-  when needsNewline newline
+  when (Choice.isTrue needsNewline) newline
   case l 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
       -- nearest enclosing span.
-      getEnclosingSpan (const True) >>= mapM_ (setSpanMark . HaddockSpan hstyle)
+      getEnclosingSpan >>= mapM_ (setSpanMark . HaddockSpan hstyle)
     RealSrcSpan spn _ -> setSpanMark (HaddockSpan hstyle spn)
 
 -- | Print anchor of named doc section.
@@ -194,4 +204,16 @@
 p_sourceText :: SourceText -> R ()
 p_sourceText = \case
   NoSourceText -> pure ()
-  SourceText s -> space >> txt (T.pack s)
+  SourceText s -> atom @FastString s
+
+p_namespaceSpec :: NamespaceSpecifier -> R ()
+p_namespaceSpec = \case
+  NoNamespaceSpecifier -> pure ()
+  TypeNamespaceSpecifier _ -> txt "type" *> space
+  DataNamespaceSpecifier _ -> txt "data" *> space
+
+p_hsMultAnn :: (mult -> R ()) -> HsMultAnnOf mult GhcPs -> R ()
+p_hsMultAnn p_mult = \case
+  HsUnannotated _ -> pure ()
+  HsLinearAnn _ -> txt "%1"
+  HsExplicitMult _ mult -> txt "%" *> p_mult mult
diff --git a/src/Ormolu/Printer/Meat/Declaration.hs b/src/Ormolu/Printer/Meat/Declaration.hs
--- a/src/Ormolu/Printer/Meat/Declaration.hs
+++ b/src/Ormolu/Printer/Meat/Declaration.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ViewPatterns #-}
@@ -11,16 +11,15 @@
   )
 where
 
+import Data.Choice (pattern Without)
 import Data.List (sort)
 import Data.List.NonEmpty (NonEmpty (..), (<|))
-import qualified Data.List.NonEmpty as NE
-import GHC.Hs.Binds
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Pat
+import Data.List.NonEmpty qualified as NE
+import GHC.Hs
 import GHC.Types.Name.Occurrence (occNameFS)
 import GHC.Types.Name.Reader
 import GHC.Types.SrcLoc
+import Ormolu.Config (SourceType (SignatureSource))
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Declaration.Annotation
@@ -59,11 +58,12 @@
 p_hsDeclsRespectGrouping = p_hsDecls' Respect
 
 p_hsDecls' :: UserGrouping -> FamilyStyle -> [LHsDecl GhcPs] -> R ()
-p_hsDecls' grouping style decls =
+p_hsDecls' grouping style decls = do
+  isSig <- (== SignatureSource) <$> askSourceType
   sepSemi id $
     -- Return a list of rendered declarations, adding a newline to separate
     -- groups.
-    case groupDecls decls of
+    case groupDecls isSig decls of
       [] -> []
       (x : xs) -> renderGroup x ++ concat (zipWith renderGroupWithPrev (x : xs) xs)
   where
@@ -75,7 +75,7 @@
         Disregard ->
           breakpoint : renderGroup curr
         Respect ->
-          if separatedByBlankNE getLoc prev curr
+          if separatedByBlankNE getLocA prev curr
             || isDocumented prev
             || isDocumented curr
             then breakpoint : renderGroup curr
@@ -90,53 +90,60 @@
     isHaddock _ = False
 
 -- | Group relevant declarations together.
-groupDecls :: [LHsDecl GhcPs] -> [NonEmpty (LHsDecl GhcPs)]
-groupDecls [] = []
-groupDecls (l@(L _ DocNext) : xs) =
+groupDecls ::
+  -- | Is the source a signature file?
+  Bool ->
+  -- | List of declarations
+  [LHsDecl GhcPs] ->
+  [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:
-  case groupDecls xs of
+  case groupDecls isSig xs of
     [] -> [l :| []]
     (x : xs') -> (l <| x) : xs'
-groupDecls (header : xs) =
+groupDecls isSig (header : xs) =
   let (grp, rest) = flip span (zip (header : xs) xs) $ \(previous, current) ->
         let relevantToHdr = groupedDecls header current
             relevantToPrev = groupedDecls previous current
-            isDeclSeries = declSeries previous current
+            isDeclSeries = not isSig && declSeries previous current
          in isDeclSeries || relevantToHdr || relevantToPrev
-   in (header :| map snd grp) : groupDecls (map snd rest)
+   in (header :| map snd grp) : groupDecls isSig (map snd rest)
 
 p_hsDecl :: FamilyStyle -> HsDecl GhcPs -> R ()
 p_hsDecl style = \case
-  TyClD NoExtField x -> p_tyClDecl style x
-  ValD NoExtField x -> p_valDecl x
-  SigD NoExtField x -> p_sigDecl x
-  InstD NoExtField x -> p_instDecl style x
-  DerivD NoExtField x -> p_standaloneDerivDecl x
-  DefD NoExtField x -> p_defaultDecl x
-  ForD NoExtField x -> p_foreignDecl x
-  WarningD NoExtField x -> p_warnDecls x
-  AnnD NoExtField x -> p_annDecl x
-  RuleD NoExtField x -> p_ruleDecls x
-  SpliceD NoExtField x -> p_spliceDecl x
-  DocD NoExtField docDecl ->
+  TyClD _ x -> p_tyClDecl style x
+  ValD _ x -> p_valDecl x
+  SigD _ x -> p_sigDecl x
+  InstD _ x -> p_instDecl style x
+  DerivD _ x -> p_standaloneDerivDecl x
+  DefD _ x -> p_defaultDecl x
+  ForD _ x -> p_foreignDecl x
+  WarningD _ x -> p_warnDecls x
+  AnnD _ x -> p_annDecl x
+  RuleD _ x -> p_ruleDecls x
+  SpliceD _ x -> p_spliceDecl x
+  DocD _ docDecl ->
     case docDecl of
-      DocCommentNext str -> p_hsDocString Pipe False (noLoc str)
-      DocCommentPrev str -> p_hsDocString Caret False (noLoc str)
-      DocCommentNamed name str -> p_hsDocString (Named name) False (noLoc str)
-      DocGroup n str -> p_hsDocString (Asterisk n) False (noLoc str)
-  RoleAnnotD NoExtField x -> p_roleAnnot x
-  KindSigD NoExtField s -> p_standaloneKindSig s
+      DocCommentNext str -> p_hsDoc Pipe (Without #endNewline) str
+      DocCommentPrev str -> p_hsDoc Caret (Without #endNewline) str
+      DocCommentNamed name str -> p_hsDoc (Named name) (Without #endNewline) str
+      DocGroup n str -> p_hsDoc (Asterisk n) (Without #endNewline) str
+  RoleAnnotD _ x -> p_roleAnnot x
+  KindSigD _ s -> p_standaloneKindSig s
 
 p_tyClDecl :: FamilyStyle -> TyClDecl GhcPs -> R ()
 p_tyClDecl style = \case
-  FamDecl NoExtField x -> p_famDecl style x
+  FamDecl _ x -> p_famDecl style x
   SynDecl {..} -> p_synDecl tcdLName tcdFixity tcdTyVars tcdRhs
   DataDecl {..} ->
     p_dataDecl
       Associated
       tcdLName
-      (tyVarsToTyPats tcdTyVars)
+      (hsq_explicit tcdTyVars)
+      getLocA
+      (located' p_hsTyVarBndr)
       tcdFixity
       tcdDataDefn
   ClassDecl {..} ->
@@ -154,16 +161,16 @@
 
 p_instDecl :: FamilyStyle -> InstDecl GhcPs -> R ()
 p_instDecl style = \case
-  ClsInstD NoExtField x -> p_clsInstDecl x
-  TyFamInstD NoExtField x -> p_tyFamInstDecl style x
-  DataFamInstD NoExtField x -> p_dataFamInstDecl style x
+  ClsInstD _ x -> p_clsInstDecl x
+  TyFamInstD _ x -> p_tyFamInstDecl style x
+  DataFamInstD _ x -> p_dataFamInstDecl style x
 
 -- | Determine if these declarations should be grouped together.
 groupedDecls ::
   LHsDecl GhcPs ->
   LHsDecl GhcPs ->
   Bool
-groupedDecls (L l_x x') (L l_y y') =
+groupedDecls (L (locA -> l_x) x') (L (locA -> l_y) y') =
   case (x', y') of
     (TypeSignature ns, FunctionBody ns') -> ns `intersects` ns'
     (TypeSignature ns, DefaultSignature ns') -> ns `intersects` ns'
@@ -174,11 +181,11 @@
     (x, DataDeclaration n) | Just ns <- isPragma x -> n `elem` ns
     (DataDeclaration n, x)
       | Just ns <- isPragma x ->
-        let f = occNameFS . rdrNameOcc in f n `elem` map f ns
+          let f = occNameFS . rdrNameOcc in f n `elem` map f ns
     (x, y)
       | Just ns <- isPragma x,
         Just ns' <- isPragma y ->
-        ns `intersects` ns'
+          ns `intersects` ns'
     (x, TypeSignature ns) | Just ns' <- isPragma x -> ns `intersects` ns'
     (TypeSignature ns, x) | Just ns' <- isPragma x -> ns `intersects` ns'
     (PatternSignature ns, Pattern n) -> n `elem` ns
@@ -200,15 +207,15 @@
   Bool
 declSeries (L _ x) (L _ y) =
   case (x, y) of
-    ( SigD NoExtField (TypeSig NoExtField _ _),
-      SigD NoExtField (TypeSig NoExtField _ _)
+    ( SigD _ (TypeSig _ _ _),
+      SigD _ (TypeSig _ _ _)
       ) -> True
     _ -> False
 
-intersects :: Ord a => [a] -> [a] -> Bool
+intersects :: (Ord a) => [a] -> [a] -> Bool
 intersects a b = go (sort a) (sort b)
   where
-    go :: Ord a => [a] -> [a] -> Bool
+    go :: (Ord a) => [a] -> [a] -> Bool
     go _ [] = False
     go [] _ = False
     go (x : xs) (y : ys)
@@ -231,7 +238,7 @@
 -- Declarations that do not refer to names
 
 pattern Splice :: HsDecl GhcPs
-pattern Splice <- SpliceD NoExtField (SpliceDecl NoExtField _ _)
+pattern Splice <- SpliceD _ (SpliceDecl _ _ _)
 
 -- Declarations referring to a single name
 
@@ -248,18 +255,24 @@
   FamilyDeclaration,
   TypeSynonym ::
     RdrName -> HsDecl GhcPs
-pattern InlinePragma n <- SigD NoExtField (InlineSig NoExtField (L _ n) _)
-pattern SpecializePragma n <- SigD NoExtField (SpecSig NoExtField (L _ n) _ _)
-pattern SCCPragma n <- SigD NoExtField (SCCFunSig NoExtField _ (L _ n) _)
-pattern AnnTypePragma n <- AnnD NoExtField (HsAnnotation NoExtField _ (TypeAnnProvenance (L _ n)) _)
-pattern AnnValuePragma n <- AnnD NoExtField (HsAnnotation NoExtField _ (ValueAnnProvenance (L _ n)) _)
-pattern Pattern n <- ValD NoExtField (PatSynBind NoExtField (PSB _ (L _ n) _ _ _))
-pattern DataDeclaration n <- TyClD NoExtField (DataDecl NoExtField (L _ n) _ _ _)
-pattern ClassDeclaration n <- TyClD NoExtField (ClassDecl _ _ (L _ n) _ _ _ _ _ _ _ _)
-pattern KindSignature n <- KindSigD NoExtField (StandaloneKindSig NoExtField (L _ n) _)
-pattern FamilyDeclaration n <- TyClD NoExtField (FamDecl NoExtField (FamilyDecl NoExtField _ (L _ n) _ _ _ _))
-pattern TypeSynonym n <- TyClD NoExtField (SynDecl NoExtField (L _ n) _ _ _)
+pattern InlinePragma n <- SigD _ (InlineSig _ (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)) _)
+pattern Pattern n <- ValD _ (PatSynBind _ (PSB _ (L _ n) _ _ _))
+pattern DataDeclaration n <- TyClD _ (DataDecl _ (L _ n) _ _ _)
+pattern ClassDeclaration n <- TyClD _ (ClassDecl _ _ (L _ n) _ _ _ _ _ _ _ _)
+pattern KindSignature n <- KindSigD _ (StandaloneKindSig _ (L _ n) _)
+pattern FamilyDeclaration n <- TyClD _ (FamDecl _ (FamilyDecl _ _ _ (L _ n) _ _ _ _))
+pattern TypeSynonym n <- TyClD _ (SynDecl _ (L _ n) _ _ _)
 
+isSpecSig :: Sig GhcPs -> Maybe RdrName
+isSpecSig = \case
+  SpecSig _ (L _ n) _ _ -> Just n
+  SpecSigE _ _ (deconstructExprFromSpecSigE -> (L _ n, _, _)) _ -> Just n
+  _ -> Nothing
+
 -- Declarations which can refer to multiple names
 
 pattern
@@ -276,47 +289,51 @@
 pattern WarningPragma n <- (warnSigRdrNames -> Just n)
 
 pattern DocNext, DocPrev :: HsDecl GhcPs
-pattern DocNext <- (DocD NoExtField (DocCommentNext _))
-pattern DocPrev <- (DocD NoExtField (DocCommentPrev _))
+pattern DocNext <- (DocD _ (DocCommentNext _))
+pattern DocPrev <- (DocD _ (DocCommentPrev _))
 
 sigRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
-sigRdrNames (SigD NoExtField (TypeSig NoExtField ns _)) = Just $ map unLoc ns
-sigRdrNames (SigD NoExtField (ClassOpSig NoExtField _ ns _)) = Just $ map unLoc ns
-sigRdrNames (SigD NoExtField (PatSynSig NoExtField ns _)) = Just $ map unLoc ns
+sigRdrNames (SigD _ (TypeSig _ ns _)) = Just $ map unLoc ns
+sigRdrNames (SigD _ (ClassOpSig _ _ ns _)) = Just $ map unLoc ns
+sigRdrNames (SigD _ (PatSynSig _ ns _)) = Just $ map unLoc ns
 sigRdrNames _ = Nothing
 
 defSigRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
-defSigRdrNames (SigD NoExtField (ClassOpSig NoExtField True ns _)) = Just $ map unLoc ns
+defSigRdrNames (SigD _ (ClassOpSig _ True ns _)) = Just $ map unLoc ns
 defSigRdrNames _ = Nothing
 
 funRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
-funRdrNames (ValD NoExtField (FunBind NoExtField (L _ n) _ _)) = Just [n]
-funRdrNames (ValD NoExtField (PatBind NoExtField (L _ n) _ _)) = Just $ patBindNames n
+funRdrNames (ValD _ (FunBind _ (L _ n) _)) = Just [n]
+funRdrNames (ValD _ (PatBind _ (L _ n) _ _)) = Just $ patBindNames n
 funRdrNames _ = Nothing
 
 patSigRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
-patSigRdrNames (SigD NoExtField (PatSynSig NoExtField ns _)) = Just $ map unLoc ns
+patSigRdrNames (SigD _ (PatSynSig _ ns _)) = Just $ map unLoc ns
 patSigRdrNames _ = Nothing
 
 warnSigRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
-warnSigRdrNames (WarningD NoExtField (Warnings NoExtField _ ws)) = Just $
-  flip concatMap ws $ \(L _ (Warning NoExtField ns _)) -> map unLoc ns
+warnSigRdrNames (WarningD _ (Warnings _ ws)) = Just $
+  flip concatMap ws $
+    \(L _ (Warning _ ns _)) -> map unLoc ns
 warnSigRdrNames _ = Nothing
 
 patBindNames :: Pat GhcPs -> [RdrName]
-patBindNames (TuplePat NoExtField ps _) = concatMap (patBindNames . unLoc) ps
-patBindNames (VarPat NoExtField (L _ n)) = [n]
-patBindNames (WildPat NoExtField) = []
-patBindNames (LazyPat NoExtField (L _ p)) = patBindNames p
-patBindNames (BangPat NoExtField (L _ p)) = patBindNames p
-patBindNames (ParPat NoExtField (L _ p)) = patBindNames p
-patBindNames (ListPat NoExtField ps) = concatMap (patBindNames . unLoc) ps
-patBindNames (AsPat NoExtField (L _ n) (L _ p)) = n : patBindNames p
-patBindNames (SumPat NoExtField (L _ p) _ _) = patBindNames p
-patBindNames (ViewPat NoExtField _ (L _ p)) = patBindNames p
-patBindNames (SplicePat NoExtField _) = []
-patBindNames (LitPat NoExtField _) = []
+patBindNames (TuplePat _ ps _) = concatMap (patBindNames . unLoc) ps
+patBindNames (OrPat _ ps) = foldMap (patBindNames . unLoc) ps
+patBindNames (VarPat _ (L _ n)) = [n]
+patBindNames (WildPat _) = []
+patBindNames (LazyPat _ (L _ p)) = patBindNames p
+patBindNames (BangPat _ (L _ p)) = patBindNames p
+patBindNames (ParPat _ (L _ p)) = patBindNames p
+patBindNames (ListPat _ ps) = concatMap (patBindNames . unLoc) ps
+patBindNames (AsPat _ (L _ n) (L _ p)) = n : patBindNames p
+patBindNames (SumPat _ (L _ p) _ _) = patBindNames p
+patBindNames (ViewPat _ _ (L _ p)) = patBindNames p
+patBindNames (SplicePat _ _) = []
+patBindNames (LitPat _ _) = []
 patBindNames (SigPat _ (L _ p) _) = patBindNames p
-patBindNames (NPat NoExtField _ _ _) = []
-patBindNames (NPlusKPat NoExtField (L _ n) _ _ _ _) = [n]
-patBindNames (ConPat NoExtField _ d) = concatMap (patBindNames . unLoc) (hsConPatArgs d)
+patBindNames (NPat _ _ _ _) = []
+patBindNames (NPlusKPat _ (L _ n) _ _ _ _) = [n]
+patBindNames (ConPat _ _ d) = concatMap (patBindNames . unLoc) (hsConPatArgs d)
+patBindNames (EmbTyPat _ _) = []
+patBindNames (InvisPat _ _) = []
diff --git a/src/Ormolu/Printer/Meat/Declaration/Annotation.hs b/src/Ormolu/Printer/Meat/Declaration/Annotation.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Annotation.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Annotation.hs
@@ -6,20 +6,19 @@
   )
 where
 
-import GHC.Hs.Decls
-import GHC.Hs.Extension
+import GHC.Hs
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Declaration.Value
 
 p_annDecl :: AnnDecl GhcPs -> R ()
-p_annDecl (HsAnnotation NoExtField _ annProv expr) =
+p_annDecl (HsAnnotation _ annProv expr) =
   pragma "ANN" . inci $ do
     p_annProv annProv
     breakpoint
     located expr p_hsExpr
 
-p_annProv :: AnnProvenance (IdP GhcPs) -> R ()
+p_annProv :: AnnProvenance GhcPs -> R ()
 p_annProv = \case
   ValueAnnProvenance name -> p_rdrName name
   TypeAnnProvenance name -> txt "type" >> space >> p_rdrName name
diff --git a/src/Ormolu/Printer/Meat/Declaration/Class.hs b/src/Ormolu/Printer/Meat/Declaration/Class.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Class.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Class.hs
@@ -13,12 +13,9 @@
 import Data.Foldable
 import Data.Function (on)
 import Data.List (sortBy)
-import GHC.Core.Class
-import GHC.Hs.Binds
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
-import GHC.Types.Basic
+import Data.Maybe
+import GHC.Hs
+import GHC.Types.Fixity
 import GHC.Types.Name.Reader
 import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
@@ -27,33 +24,32 @@
 import Ormolu.Printer.Meat.Type
 
 p_classDecl ::
-  LHsContext GhcPs ->
-  Located RdrName ->
+  Maybe (LHsContext GhcPs) ->
+  LocatedN RdrName ->
   LHsQTyVars GhcPs ->
   LexicalFixity ->
-  [Located (FunDep (Located RdrName))] ->
+  [LHsFunDep GhcPs] ->
   [LSig GhcPs] ->
   LHsBinds GhcPs ->
   [LFamilyDecl GhcPs] ->
   [LTyFamDefltDecl GhcPs] ->
-  [LDocDecl] ->
+  [LDocDecl GhcPs] ->
   R ()
 p_classDecl ctx name HsQTvs {..} fixity fdeps csigs cdefs cats catdefs cdocs = do
-  let variableSpans = getLoc <$> hsq_explicit
-      signatureSpans = getLoc name : variableSpans
-      dependencySpans = getLoc <$> fdeps
-      combinedSpans = getLoc ctx : (signatureSpans ++ dependencySpans)
+  let variableSpans = getLocA <$> hsq_explicit
+      signatureSpans = getLocA name : variableSpans
+      dependencySpans = getLocA <$> fdeps
+      combinedSpans = maybeToList (getLocA <$> ctx) ++ signatureSpans ++ dependencySpans
       -- GHC's AST does not necessarily store each kind of element in source
       -- location order. This happens because different declarations are stored
       -- in different lists. Consequently, to get all the declarations in proper
       -- order, they need to be manually sorted.
-      sigs = (getLoc &&& fmap (SigD NoExtField)) <$> csigs
-      vals = (getLoc &&& fmap (ValD NoExtField)) <$> toList cdefs
-      tyFams = (getLoc &&& fmap (TyClD NoExtField . FamDecl NoExtField)) <$> cats
-      docs = (getLoc &&& fmap (DocD NoExtField)) <$> cdocs
+      sigs = (getLocA &&& fmap (SigD NoExtField)) <$> csigs
+      vals = (getLocA &&& fmap (ValD NoExtField)) <$> toList cdefs
+      tyFams = (getLocA &&& fmap (TyClD NoExtField . FamDecl NoExtField)) <$> cats
+      docs = (getLocA &&& fmap (DocD NoExtField)) <$> cdocs
       tyFamDefs =
-        ( getLoc &&& fmap (InstD NoExtField . TyFamInstD NoExtField)
-        )
+        (getLocA &&& fmap (InstD NoExtField . TyFamInstD NoExtField))
           <$> catdefs
       allDecls =
         snd <$> sortBy (leftmost_smallest `on` fst) (sigs <> vals <> tyFams <> tyFamDefs <> docs)
@@ -61,7 +57,7 @@
   switchLayout combinedSpans $ do
     breakpoint
     inci $ do
-      p_classContext ctx
+      for_ ctx p_classContext
       switchLayout signatureSpans $
         p_infixDefHelper
           (isInfix fixity)
@@ -83,15 +79,15 @@
   txt "=>"
   breakpoint
 
-p_classFundeps :: [Located (FunDep (Located RdrName))] -> R ()
+p_classFundeps :: [LHsFunDep GhcPs] -> R ()
 p_classFundeps fdeps = unless (null fdeps) $ do
   breakpoint
   txt "|"
   space
   inci $ sep commaDel (sitcc . located' p_funDep) fdeps
 
-p_funDep :: FunDep (Located RdrName) -> R ()
-p_funDep (before, after) = do
+p_funDep :: FunDep GhcPs -> R ()
+p_funDep (FunDep _ before after) = do
   sep space p_rdrName before
   space
   txt "->"
diff --git a/src/Ormolu/Printer/Meat/Declaration/Data.hs b/src/Ormolu/Printer/Meat/Declaration/Data.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Data.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Data.hs
@@ -1,6 +1,10 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Renedring of data type declarations.
@@ -10,12 +14,13 @@
 where
 
 import Control.Monad
-import Data.Maybe (isJust, maybeToList)
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
-import GHC.Parser.Annotation
-import GHC.Types.Basic
+import Data.Choice (Choice, pattern Is, pattern Isn't, pattern With)
+import Data.Choice qualified as Choice
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty qualified as NE
+import Data.Maybe (isJust, isNothing, maybeToList)
+import GHC.Hs
+import GHC.Types.Fixity
 import GHC.Types.ForeignCall
 import GHC.Types.Name.Reader
 import GHC.Types.SrcLoc
@@ -28,172 +33,231 @@
   -- | Whether to format as data family
   FamilyStyle ->
   -- | Type constructor
-  Located RdrName ->
-  -- | Type patterns
-  HsTyPats GhcPs ->
+  LocatedN RdrName ->
+  -- | Type variables
+  [tyVar] ->
+  -- | Get location information for type variables
+  (tyVar -> SrcSpan) ->
+  -- | How to print type variables
+  (tyVar -> R ()) ->
   -- | Lexical fixity
   LexicalFixity ->
   -- | Data definition
   HsDataDefn GhcPs ->
   R ()
-p_dataDecl style name tpats fixity HsDataDefn {..} = do
-  txt $ case dd_ND of
-    NewType -> "newtype"
-    DataType -> "data"
+p_dataDecl style name tyVars getTyVarLoc p_tyVar fixity HsDataDefn {..} = do
+  txt $ case dd_cons of
+    NewTypeCon _ -> "newtype"
+    DataTypeCons False _ -> "data"
+    DataTypeCons True _ -> "type data"
   txt $ case style of
     Associated -> mempty
     Free -> " instance"
-  case unLoc <$> dd_cType of
-    Nothing -> pure ()
-    Just (CType prag header (type_, _)) -> do
-      p_sourceText prag
-      case header of
-        Nothing -> pure ()
-        Just (Header h _) -> space *> p_sourceText h
-      p_sourceText type_
-      txt " #-}"
-  let constructorSpans = getLoc name : fmap lhsTypeArgSrcSpan tpats
-  switchLayout constructorSpans $ do
+  let constructorSpans = getLocA name : fmap getTyVarLoc tyVars
+      sigSpans = maybeToList . fmap getLocA $ dd_kindSig
+      contextSpans = maybeToList . fmap getLocA $ dd_ctxt
+      ctypeSpans = maybeToList . fmap getLocA $ dd_cType
+      declHeaderSpans =
+        constructorSpans ++ sigSpans ++ contextSpans ++ ctypeSpans
+  switchLayout declHeaderSpans . inci $ do
+    case unLoc <$> dd_cType of
+      Nothing -> pure ()
+      Just (CType prag header (type_, _)) -> do
+        breakpoint
+        p_sourceText prag
+        case header of
+          Nothing -> pure ()
+          Just (Header h _) -> space *> p_sourceText h
+        space
+        p_sourceText type_
+        txt " #-}"
     breakpoint
-    inci $
+    forM_ dd_ctxt p_lhsContext
+    switchLayout constructorSpans $
       p_infixDefHelper
         (isInfix fixity)
         True
         (p_rdrName name)
-        (p_lhsTypeArg <$> tpats)
-  case dd_kindSig of
-    Nothing -> return ()
-    Just k -> do
+        (p_tyVar <$> tyVars)
+    forM_ dd_kindSig $ \k -> do
       space
       txt "::"
-      space
-      located k p_hsType
-  let gadt = isJust dd_kindSig || any (isGadt . unLoc) dd_cons
-  unless (null dd_cons) $
-    if gadt
-      then inci $ do
-        switchLayout constructorSpans $ do
+      breakpoint
+      inci $ located k p_hsType
+  let dd_cons' = case dd_cons of
+        NewTypeCon a -> [a]
+        DataTypeCons _ as -> as
+      gadt = isJust dd_kindSig || any (isGadt . unLoc) dd_cons'
+  case dd_cons' of
+    [] -> pure ()
+    first_dd_cons : _ ->
+      if gadt
+        then inci $ do
+          switchLayout declHeaderSpans $ do
+            breakpoint
+            txt "where"
           breakpoint
-          txt "where"
-        breakpoint
-        sepSemi (located' (p_conDecl False)) dd_cons
-      else switchLayout (getLoc name : (getLoc <$> dd_cons)) . inci $ do
-        let singleConstRec = isSingleConstRec dd_cons
-        if singleConstRec
-          then space
-          else
-            if hasHaddocks dd_cons
-              then newline
-              else breakpoint
-        equals
-        space
-        layout <- getLayout
-        let s =
-              if layout == MultiLine || hasHaddocks dd_cons
-                then newline >> txt "|" >> space
-                else space >> txt "|" >> space
-            sitcc' =
-              if singleConstRec
-                then id
-                else sitcc
-        sep s (sitcc' . located' (p_conDecl singleConstRec)) dd_cons
-  unless (null $ unLoc dd_derivs) breakpoint
-  inci . located dd_derivs $ \xs ->
-    sep newline (located' p_hsDerivingClause) xs
+          sepSemi (located' (p_conDecl (Isn't #singleRecCon))) dd_cons'
+        else switchLayout (getLocA name : (getLocA <$> dd_cons')) . inci $ do
+          let singleRecCon =
+                case dd_cons' of
+                  [L _ ConDeclH98 {con_args = RecCon {}}] -> Is #singleRecCon
+                  _ -> Isn't #singleRecCon
+              compactLayoutAroundEquals =
+                onTheSameLine
+                  (getLocA name)
+                  (combineSrcSpans' (conDeclConsSpans (unLoc first_dd_cons)))
+              conDeclConsSpans = \case
+                ConDeclGADT {..} -> getLocA <$> con_names
+                ConDeclH98 {..} -> getLocA con_name :| []
+          if hasHaddocks dd_cons'
+            then newline
+            else
+              if Choice.isTrue singleRecCon && compactLayoutAroundEquals
+                then space
+                else breakpoint
+          equals
+          space
+          layout <- getLayout
+          let s =
+                if layout == MultiLine || hasHaddocks dd_cons'
+                  then newline >> txt "|" >> space
+                  else space >> txt "|" >> space
+              sitcc' =
+                if hasHaddocks dd_cons' || Choice.isFalse singleRecCon
+                  then sitcc
+                  else id
+          sep s (sitcc' . located' (p_conDecl singleRecCon)) dd_cons'
+  unless (null dd_derivs) breakpoint
+  inci $ sep newline (located' p_hsDerivingClause) dd_derivs
 
-p_conDecl ::
-  Bool ->
-  ConDecl GhcPs ->
-  R ()
-p_conDecl singleConstRec = \case
-  ConDeclGADT {..} -> do
-    mapM_ (p_hsDocString Pipe True) con_doc
-    let conDeclSpn =
-          fmap getLoc con_names
-            <> [getLoc con_forall]
-            <> fmap getLoc con_qvars
-            <> maybeToList (fmap getLoc con_mb_cxt)
-            <> conArgsSpans con_args
-    switchLayout conDeclSpn $ do
-      case con_names of
-        [] -> return ()
-        (c : cs) -> do
-          p_rdrName c
-          unless (null cs) . inci $ do
-            commaDel
-            sep commaDel p_rdrName cs
-      inci $ do
+p_conDecl :: Choice "singleRecCon" -> ConDecl GhcPs -> R ()
+p_conDecl _ ConDeclGADT {..} = do
+  mapM_ (p_hsDoc Pipe (With #endNewline)) con_doc
+  switchLayout conDeclSpn $ do
+    let c :| cs = con_names
+    p_rdrName c
+    unless (null cs) . inci $ do
+      commaDel
+      sep commaDel p_rdrName cs
+    space
+    txt "::"
+    delimiter
+    inci . switchLayout conSigSpans $ do
+      located con_outer_bndrs p_hsOuterTyVarBndrs
+      case unLoc con_outer_bndrs of
+        HsOuterImplicit {} -> pure ()
+        HsOuterExplicit {} -> delimiter
+      forM_ con_inner_bndrs $ \tele -> do
+        p_hsForAllTelescope tele
+        delimiter
+      forM_ con_mb_cxt $ \qs -> do
+        located qs p_hsContext
         space
-        txt "::"
-        let interArgBreak =
-              if hasDocStrings (unLoc con_res_ty)
-                then newline
-                else breakpoint
-        interArgBreak
-        conTy <- case con_args of
-          PrefixCon xs ->
-            let go (HsScaled a b) t = L (combineLocs t b) (HsFunTy NoExtField a b t)
-             in pure $ foldr go con_res_ty xs
-          RecCon r@(L l rs) ->
-            pure
-              . L (combineLocs r con_res_ty)
-              $ HsFunTy
-                NoExtField
-                (HsUnrestrictedArrow NormalSyntax)
-                (L l $ HsRecTy NoExtField rs)
-                con_res_ty
-          InfixCon _ _ -> notImplemented "InfixCon" -- NOTE(amesgen) should be unreachable
-        let qualTy = case con_mb_cxt of
-              Nothing -> conTy
-              Just qs ->
-                L (combineLocs qs conTy) $
-                  HsQualTy NoExtField qs conTy
-        let quantifiedTy =
-              if unLoc con_forall
-                then
-                  L (combineLocs con_forall qualTy) $
-                    HsForAllTy NoExtField (mkHsForAllInvisTele con_qvars) qualTy
-                else qualTy
-        p_hsType (unLoc quantifiedTy)
-  ConDeclH98 {..} -> do
-    mapM_ (p_hsDocString Pipe True) con_doc
-    let conDeclWithContextSpn =
-          [getLoc con_forall]
-            <> fmap getLoc con_ex_tvs
-            <> maybeToList (fmap getLoc con_mb_cxt)
-            <> conDeclSpn
-        conDeclSpn =
-          getLoc con_name : conArgsSpans con_args
-    switchLayout conDeclWithContextSpn $ do
-      when (unLoc con_forall) $ do
-        p_forallBndrs ForAllInvis p_hsTyVarBndr con_ex_tvs
+        txt "=>"
+        delimiter
+      switchLayout conArgResSpans $ do
+        case con_g_args of
+          PrefixConGADT NoExtField xs ->
+            forM_ xs $ \x -> do
+              p_hsConDeclFieldWithDoc x
+              space
+              p_hsMultAnn (located' p_hsType) (cdf_multiplicity x)
+              space
+              txt "->"
+              delimiter
+          RecConGADT _ x -> do
+            located x p_hsConDeclRecFields
+            space
+            txt "->"
+            delimiter
+        located con_res_ty p_hsType
+  where
+    delimiter = if anyDocStrings then newline else breakpoint
+    anyDocStrings =
+      hasDocStrings (unLoc con_res_ty) || case con_g_args of
+        PrefixConGADT _ xs -> conArgsHaveHaddocks xs
+        RecConGADT _ (L _ xs) -> conArgsHaveHaddocks $ cdrf_spec . unLoc <$> xs
+
+    conDeclSpn =
+      fmap getLocA (NE.toList con_names) <> conSigSpans
+    conSigSpans =
+      [getLocA con_outer_bndrs]
+        <> maybeToList (fmap getLocA con_mb_cxt)
+        <> 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 xs -> do
+      renderConDoc
+      renderContext
+      switchLayout conDeclSpn $ do
+        p_rdrName con_name
+        let argsHaveDocs = conArgsHaveHaddocks xs
+            delimiter = if argsHaveDocs then newline else breakpoint
+        unless (null xs) delimiter
+        inci . sitcc $
+          sep delimiter (sitcc . p_hsConDeclFieldWithDoc) xs
+    RecCon l -> do
+      renderConDoc
+      renderContext
+      switchLayout conDeclSpn $ do
+        p_rdrName con_name
         breakpoint
-      forM_ con_mb_cxt p_lhsContext
-      switchLayout conDeclSpn $ case con_args of
-        PrefixCon xs -> do
-          p_rdrName con_name
-          unless (null xs) breakpoint
-          inci . sitcc $ sep breakpoint (sitcc . located' p_hsTypePostDoc) (hsScaledThing <$> xs)
-        RecCon l -> do
+        inciIf (Choice.isFalse singleRecCon) (located l p_hsConDeclRecFields)
+    InfixCon l r -> do
+      -- manually render these
+      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
+      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
+        if isJust con_doc
+          then do
+            mapM_ (p_hsDoc Pipe (With #endNewline)) larg_doc
+            p_hsConDeclField l
+            breakpoint
+          else do
+            p_hsConDeclField l
+            case larg_doc of
+              Just doc -> space >> p_hsDoc Caret (With #endNewline) doc
+              Nothing -> breakpoint
+        inci $ do
+          unless putConDocOnTop renderConDoc
           p_rdrName con_name
-          breakpoint
-          inciIf (not singleConstRec) (located l p_conDeclFields)
-        InfixCon (HsScaled _ x) (HsScaled _ y) -> do
-          located x p_hsType
+          case rarg_doc of
+            Just doc -> newline >> p_hsDoc Pipe (With #endNewline) doc
+            Nothing -> breakpoint
+          p_hsConDeclField r
+  where
+    renderConDoc = mapM_ (p_hsDoc Pipe (With #endNewline)) con_doc
+    renderContext =
+      switchLayout conNameWithContextSpn $ do
+        when con_forall $ do
+          p_forallBndrs ForAllInvis p_hsTyVarBndr con_ex_tvs
           breakpoint
-          inci $ do
-            p_rdrName con_name
-            space
-            located y p_hsType
+        forM_ con_mb_cxt p_lhsContext
 
-conArgsSpans :: HsConDeclDetails GhcPs -> [SrcSpan]
-conArgsSpans = \case
-  PrefixCon xs ->
-    getLoc . hsScaledThing <$> xs
-  RecCon l ->
-    [getLoc l]
-  InfixCon x y ->
-    getLoc . hsScaledThing <$> [x, y]
+    conNameWithContextSpn =
+      [getHasLoc $ acdh_forall con_ext]
+        <> fmap getLocA con_ex_tvs
+        <> maybeToList (fmap getLocA con_mb_cxt)
+        <> [conNameSpn]
+    conDeclSpn = conNameSpn : conArgsSpans
+    conNameSpn = getLocA con_name
+    conArgsSpans = case con_args of
+      PrefixCon xs -> getLocA . cdf_type <$> xs
+      RecCon l -> [getLocA l]
+      InfixCon x y -> getLocA . cdf_type <$> [x, y]
 
 p_lhsContext ::
   LHsContext GhcPs ->
@@ -217,39 +281,39 @@
 p_hsDerivingClause HsDerivingClause {..} = do
   txt "deriving"
   let derivingWhat = located deriv_clause_tys $ \case
-        [] -> txt "()"
-        xs ->
+        DctSingle NoExtField sigTy -> parens N $ located sigTy p_hsSigType
+        DctMulti NoExtField sigTys ->
           parens N $
             sep
               commaDel
-              (sitcc . located' p_hsType . hsib_body)
-              xs
+              (sitcc . located' p_hsSigType)
+              sigTys
   space
   case deriv_clause_strategy of
     Nothing -> do
       breakpoint
       inci derivingWhat
     Just (L _ a) -> case a of
-      StockStrategy -> do
+      StockStrategy _ -> do
         txt "stock"
         breakpoint
         inci derivingWhat
-      AnyclassStrategy -> do
+      AnyclassStrategy _ -> do
         txt "anyclass"
         breakpoint
         inci derivingWhat
-      NewtypeStrategy -> do
+      NewtypeStrategy _ -> do
         txt "newtype"
         breakpoint
         inci derivingWhat
-      ViaStrategy HsIB {..} -> do
+      ViaStrategy (XViaStrategyPs _ sigTy) -> do
         breakpoint
         inci $ do
           derivingWhat
           breakpoint
           txt "via"
           space
-          located hsib_body p_hsType
+          located sigTy p_hsSigType
 
 ----------------------------------------------------------------------------
 -- Helpers
@@ -259,15 +323,14 @@
   Infix -> True
   Prefix -> False
 
-isSingleConstRec :: [LConDecl GhcPs] -> Bool
-isSingleConstRec [(L _ ConDeclH98 {..})] =
-  case con_args of
-    RecCon _ -> True
-    _ -> False
-isSingleConstRec _ = False
-
 hasHaddocks :: [LConDecl GhcPs] -> Bool
 hasHaddocks = any (f . unLoc)
   where
-    f ConDeclH98 {..} = isJust con_doc
+    f ConDeclH98 {..} =
+      isJust con_doc || case con_args of
+        PrefixCon xs -> conArgsHaveHaddocks xs
+        _ -> False
     f _ = False
+
+conArgsHaveHaddocks :: [HsConDeclField GhcPs] -> Bool
+conArgsHaveHaddocks = any (isJust . cdf_doc)
diff --git a/src/Ormolu/Printer/Meat/Declaration/Default.hs b/src/Ormolu/Printer/Meat/Declaration/Default.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Default.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Default.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Ormolu.Printer.Meat.Declaration.Default
@@ -6,14 +5,18 @@
   )
 where
 
-import GHC.Hs.Decls
-import GHC.Hs.Extension
+import GHC.Data.Maybe (whenIsJust)
+import GHC.Hs
 import Ormolu.Printer.Combinators
+import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Type
 
 p_defaultDecl :: DefaultDecl GhcPs -> R ()
-p_defaultDecl (DefaultDecl NoExtField ts) = do
+p_defaultDecl (DefaultDecl _ mclass ts) = do
   txt "default"
+  whenIsJust mclass $ \c -> do
+    breakpoint
+    p_rdrName c
   breakpoint
   inci . parens N $
     sep commaDel (sitcc . located' p_hsType) ts
diff --git a/src/Ormolu/Printer/Meat/Declaration/Foreign.hs b/src/Ormolu/Printer/Meat/Declaration/Foreign.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Foreign.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Foreign.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 module Ormolu.Printer.Meat.Declaration.Foreign
@@ -8,14 +7,14 @@
 where
 
 import Control.Monad
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
+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
@@ -33,12 +32,12 @@
   breakpoint
   inci
     . switchLayout
-      [ getLoc (fd_name fd),
-        (getLoc . hsib_body . fd_sig_ty) fd
+      [ getLocA (fd_name fd),
+        (getLocA . fd_sig_ty) fd
       ]
     $ do
       p_rdrName (fd_name fd)
-      p_typeAscription (HsWC NoExtField (fd_sig_ty fd))
+      p_typeAscription (fd_sig_ty fd)
 
 -- | Printer for 'ForeignImport'.
 --
@@ -52,18 +51,23 @@
 -- We also layout the identifier using the 'SourceText', because printing
 -- with the other two fields of 'CImport' is very complicated. See the
 -- 'Outputable' instance of 'ForeignImport' for details.
-p_foreignImport :: ForeignImport -> R ()
-p_foreignImport (CImport cCallConv safety _ _ sourceText) = do
+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
-  when (isGoodSrcSpan $ getLoc safety) (space >> atom safety)
-  located sourceText p_sourceText
+  when (isGoodSrcSpan $ getLocA safety) (space >> atom safety)
+  inci $ located sourceText $ \case
+    NoSourceText -> pure ()
+    SourceText lit -> do
+      breakpoint
+      p_stringLit lit
 
-p_foreignExport :: ForeignExport -> R ()
-p_foreignExport (CExport (L loc (CExportStatic _ _ cCallConv)) sourceText) = do
+p_foreignExport :: ForeignExport GhcPs -> R ()
+p_foreignExport (CExport sourceText (L loc (CExportStatic _ _ cCallConv))) = do
   txt "foreign export"
   space
   located (L loc cCallConv) atom
+  space
   located sourceText p_sourceText
diff --git a/src/Ormolu/Printer/Meat/Declaration/Instance.hs b/src/Ormolu/Printer/Meat/Declaration/Instance.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Instance.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Instance.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
@@ -16,9 +15,8 @@
 import Data.Foldable
 import Data.Function (on)
 import Data.List (sortBy)
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
+import Data.Maybe (maybeToList)
+import GHC.Hs
 import GHC.Types.Basic
 import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
@@ -26,13 +24,17 @@
 import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration
 import Ormolu.Printer.Meat.Declaration.Data
 import Ormolu.Printer.Meat.Declaration.TypeFamily
+import Ormolu.Printer.Meat.Declaration.Warning
 import Ormolu.Printer.Meat.Type
 
 p_standaloneDerivDecl :: DerivDecl GhcPs -> R ()
-p_standaloneDerivDecl DerivDecl {..} = do
-  let typesAfterInstance = located (hsib_body (hswc_body deriv_type)) p_hsType
+p_standaloneDerivDecl DerivDecl {deriv_ext = (mWarnTxt, _), ..} = do
+  let typesAfterInstance = located (hswc_body deriv_type) p_hsSigType
       instTypes toIndent = inci $ do
         txt "instance"
+        for_ mWarnTxt $ \warnTxt -> do
+          breakpoint
+          located warnTxt p_warningTxt
         breakpoint
         match_overlap_mode deriv_overlap_mode breakpoint
         inciIf toIndent typesAfterInstance
@@ -42,47 +44,47 @@
     Nothing ->
       instTypes False
     Just (L _ a) -> case a of
-      StockStrategy -> do
+      StockStrategy _ -> do
         txt "stock "
         instTypes False
-      AnyclassStrategy -> do
+      AnyclassStrategy _ -> do
         txt "anyclass "
         instTypes False
-      NewtypeStrategy -> do
+      NewtypeStrategy _ -> do
         txt "newtype "
         instTypes False
-      ViaStrategy HsIB {..} -> do
+      ViaStrategy (XViaStrategyPs _ sigTy) -> do
         txt "via"
         breakpoint
-        inci (located hsib_body p_hsType)
+        inci (located sigTy p_hsSigType)
         breakpoint
         instTypes True
 
 p_clsInstDecl :: ClsInstDecl GhcPs -> R ()
-p_clsInstDecl ClsInstDecl {..} = do
+p_clsInstDecl ClsInstDecl {cid_ext = (mWarnTxt, _, _), ..} = do
   txt "instance"
-  let HsIB {..} = cid_poly_ty
   -- GHC's AST does not necessarily store each kind of element in source
   -- location order. This happens because different declarations are stored in
   -- different lists. Consequently, to get all the declarations in proper
   -- order, they need to be manually sorted.
-  let sigs = (getLoc &&& fmap (SigD NoExtField)) <$> cid_sigs
-      vals = (getLoc &&& fmap (ValD NoExtField)) <$> toList cid_binds
+  let sigs = (getLocA &&& fmap (SigD NoExtField)) <$> cid_sigs
+      vals = (getLocA &&& fmap (ValD NoExtField)) <$> toList cid_binds
       tyFamInsts =
-        ( getLoc &&& fmap (InstD NoExtField . TyFamInstD NoExtField)
-        )
+        (getLocA &&& fmap (InstD NoExtField . TyFamInstD NoExtField))
           <$> cid_tyfam_insts
       dataFamInsts =
-        ( getLoc &&& fmap (InstD NoExtField . DataFamInstD NoExtField)
-        )
+        (getLocA &&& fmap (InstD NoExtField . DataFamInstD NoExtField))
           <$> cid_datafam_insts
       allDecls =
         snd <$> sortBy (leftmost_smallest `on` fst) (sigs <> vals <> tyFamInsts <> dataFamInsts)
-  located hsib_body $ \x -> do
+  switchLayout (maybeToList (getLocA <$> mWarnTxt) <> [getLocA cid_poly_ty]) $ do
+    for_ mWarnTxt $ \warnTxt -> do
+      breakpoint
+      located warnTxt p_warningTxt
     breakpoint
-    inci $ do
+    located cid_poly_ty $ \sigTy -> inci $ do
       match_overlap_mode cid_overlap_mode breakpoint
-      p_hsType x
+      p_hsSigType sigTy
       unless (null allDecls) $ do
         breakpoint
         txt "where"
@@ -100,10 +102,17 @@
   inci (p_tyFamInstEqn tfid_eqn)
 
 p_dataFamInstDecl :: FamilyStyle -> DataFamInstDecl GhcPs -> R ()
-p_dataFamInstDecl style (DataFamInstDecl {dfid_eqn = HsIB {hsib_body = FamEqn {..}}}) =
-  p_dataDecl style feqn_tycon feqn_pats feqn_fixity feqn_rhs
+p_dataFamInstDecl style (DataFamInstDecl {dfid_eqn = FamEqn {..}}) =
+  p_dataDecl
+    style
+    feqn_tycon
+    feqn_pats
+    lhsTypeArgSrcSpan
+    p_lhsTypeArg
+    feqn_fixity
+    feqn_rhs
 
-match_overlap_mode :: Maybe (Located OverlapMode) -> R () -> R ()
+match_overlap_mode :: Maybe (LocatedP OverlapMode) -> R () -> R ()
 match_overlap_mode overlap_mode layoutStrategy =
   case unLoc <$> overlap_mode of
     Just Overlappable {} -> do
diff --git a/src/Ormolu/Printer/Meat/Declaration/OpTree.hs b/src/Ormolu/Printer/Meat/Declaration/OpTree.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Printer/Meat/Declaration/OpTree.hs
@@ -0,0 +1,244 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+
+-- | Printing of operator trees.
+module Ormolu.Printer.Meat.Declaration.OpTree
+  ( p_exprOpTree,
+    exprOpTree,
+    p_cmdOpTree,
+    cmdOpTree,
+    p_tyOpTree,
+    tyOpTree,
+    getOpName,
+    getOpNameStr,
+  )
+where
+
+import Data.Functor ((<&>))
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty qualified as NE
+import GHC.Hs
+import GHC.Types.Fixity
+import GHC.Types.Name (occNameString)
+import GHC.Types.Name.Reader (RdrName, rdrNameOcc)
+import GHC.Types.SrcLoc
+import Ormolu.Printer.Combinators
+import Ormolu.Printer.Meat.Common (p_rdrName)
+import Ormolu.Printer.Meat.Declaration.Value
+  ( IsApplicand (..),
+    cmdTopPlacement,
+    exprPlacement,
+    p_hsCmdTop,
+    p_hsExpr,
+    p_hsExpr',
+  )
+import Ormolu.Printer.Meat.Type (p_hsType)
+import Ormolu.Printer.Operators
+
+-- | Extract the operator name of the specified 'HsExpr' if this expression
+-- corresponds to an operator.
+getOpName :: HsExpr GhcPs -> Maybe RdrName
+getOpName = \case
+  HsVar _ (L _ a) -> Just a
+  _ -> Nothing
+
+-- | Convert an operator name to a 'String'.
+getOpNameStr :: RdrName -> String
+getOpNameStr = occNameString . rdrNameOcc
+
+-- | Decide if the operands of an operator chain should be hanging.
+opBranchPlacement ::
+  (HasLoc l) =>
+  -- | Placer function for nodes
+  (ty -> Placement) ->
+  -- | first expression of the chain
+  OpTree (GenLocated l ty) op ->
+  -- | 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.
+  | isOneLineSpan
+      ( mkSrcSpan
+          (srcSpanStart (opTreeLoc firstExpr))
+          (srcSpanStart (opTreeLoc lastExpr))
+      ),
+    OpNode (L _ n) <- lastExpr =
+      placer n
+  | otherwise = Normal
+
+-- | Decide whether to use braces or not based on the layout and placement
+-- of an expression in an infix operator application.
+opBranchBraceStyle :: Placement -> R (R () -> R ())
+opBranchBraceStyle placement =
+  getLayout <&> \case
+    SingleLine -> useBraces
+    MultiLine -> case placement of
+      Hanging -> useBraces
+      Normal -> dontUseBraces
+
+-- | Convert a 'LHsExpr' containing an operator tree to the 'OpTree'
+-- intermediate representation.
+exprOpTree :: LHsExpr GhcPs -> OpTree (LHsExpr GhcPs) (LHsExpr GhcPs)
+exprOpTree (L _ (OpApp _ x op y)) = BinaryOpBranches (exprOpTree x) op (exprOpTree y)
+exprOpTree n = OpNode n
+
+-- | Print an operator tree where leaves are values.
+p_exprOpTree ::
+  -- | Bracket style to use
+  BracketStyle ->
+  -- | N-ary 'OpTree' to render, enhanced with information regarding
+  -- operator fixity
+  OpTree (LHsExpr GhcPs) (OpInfo (LHsExpr GhcPs)) ->
+  R ()
+p_exprOpTree s (OpNode x) = located x (p_hsExpr' NotApplicand s)
+p_exprOpTree s t@(OpBranches exprs@(firstExpr :| otherExprs) ops) = do
+  let placement =
+        opBranchPlacement
+          exprPlacement
+          firstExpr
+          (last otherExprs)
+      rightMostNode = \case
+        n@(OpNode _) -> n
+        OpBranches exprs'' _ -> rightMostNode (NE.last exprs'')
+      isDoBlock = \case
+        OpNode (L _ (HsDo _ ctx _)) -> case ctx of
+          DoExpr _ -> True
+          MDoExpr _ -> True
+          _ -> False
+        _ -> False
+      -- Whether we could place the operator in a trailing position,
+      -- followed by a breakpoint before the RHS
+      couldBeTrailing (prevExpr, opi) =
+        -- An operator with fixity InfixR 0, like seq, $, and $ variants,
+        -- is required
+        isHardSplitterOp (opiFixityApproximation opi)
+          -- the LHS must be single-line
+          && isOneLineSpan (opTreeLoc prevExpr)
+          -- 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)
+          && 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
+  ub <- if isTrailing then return useBraces else opBranchBraceStyle placement
+  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
+            p_op = located (opiOp opi) p_hsExpr
+            p_y = ub' $ p_exprOpTree N expr
+        if isTrailing
+          then do
+            space
+            p_op
+            placeHanging
+              -- When we have a chain of trailing operators (staircase style),
+              -- the last operand, when multiline, is allowed to hang
+              -- (ex: do block, lambda...)
+              ( if isLast && (not . isOneLineSpan . opTreeLoc $ expr)
+                  then opBranchPlacement exprPlacement prevExpr expr
+                  else Normal
+              )
+              $ do
+                p_y
+                putOpsExprs expr ops' exprs'
+          else do
+            placeHanging placement $ do
+              p_op
+              space
+              p_y
+            putOpsExprs expr ops' exprs'
+      putOpsExprs _ _ _ = pure ()
+  switchLayout [opTreeLoc t] $ do
+    p_x
+    putOpsExprs firstExpr ops otherExprs
+
+-- | Convert a 'LHsCmdTop' containing an operator tree to the 'OpTree'
+-- intermediate representation.
+cmdOpTree :: LHsCmdTop GhcPs -> OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs)
+cmdOpTree = \case
+  (L _ (HsCmdTop _ (L _ (HsCmdArrForm _ op Infix [x, y])))) ->
+    BinaryOpBranches (cmdOpTree x) op (cmdOpTree y)
+  n -> OpNode n
+
+-- | Print an operator tree where leaves are commands.
+p_cmdOpTree ::
+  -- | Bracket style to use
+  BracketStyle ->
+  -- | N-ary OpTree to render, enhanced with information regarding operator
+  -- fixity
+  OpTree (LHsCmdTop GhcPs) (OpInfo (LHsExpr GhcPs)) ->
+  R ()
+p_cmdOpTree s (OpNode x) = located x (p_hsCmdTop s)
+p_cmdOpTree s t@(OpBranches (firstExpr :| otherExprs) ops) = do
+  let placement =
+        opBranchPlacement
+          cmdTopPlacement
+          firstExpr
+          (last otherExprs)
+  ub <- opBranchBraceStyle placement
+  let p_x = ub $ p_cmdOpTree s firstExpr
+      putOpsExprs (opi : ops') (expr : exprs') = do
+        let ub' = if not (null exprs') then ub else id
+            p_op = located (opiOp opi) p_hsExpr
+            p_y = ub' $ p_cmdOpTree N expr
+        placeHanging placement $ do
+          p_op
+          space
+          p_y
+        putOpsExprs ops' exprs'
+      putOpsExprs _ _ = pure ()
+  switchLayout [opTreeLoc t] $ do
+    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.
+tyOpPlacement :: HsType GhcPs -> Placement
+tyOpPlacement = \case
+  _ -> Normal
+
+-- | Convert a LHsType containing an operator tree to the 'OpTree'
+-- intermediate representation.
+tyOpTree :: LHsType GhcPs -> OpTree (LHsType GhcPs) (LocatedN RdrName)
+tyOpTree (L _ (HsOpTy _ _ l op r)) =
+  BinaryOpBranches (tyOpTree l) op (tyOpTree r)
+tyOpTree n = OpNode n
+
+-- | Print an operator tree where leaves are types.
+p_tyOpTree ::
+  -- | N-ary 'OpTree' to render, enhanced with information regarding
+  -- operator fixity
+  OpTree (LHsType GhcPs) (OpInfo (LocatedN RdrName)) ->
+  R ()
+p_tyOpTree (OpNode n) = located n p_hsType
+p_tyOpTree t@(OpBranches (firstExpr :| otherExprs) ops) = do
+  let placement =
+        opBranchPlacement
+          tyOpPlacement
+          firstExpr
+          (last otherExprs)
+      p_x = p_tyOpTree firstExpr
+      putOpsExprs (opi : ops') (expr : exprs') = do
+        let p_op = p_rdrName (opiOp opi)
+            p_y = p_tyOpTree expr
+        placeHanging
+          placement
+          $ do
+            p_op
+            space
+            p_y
+        putOpsExprs ops' exprs'
+      putOpsExprs _ _ = pure ()
+  switchLayout [opTreeLoc t] $ do
+    ub <- opBranchBraceStyle placement
+    ub p_x
+    putOpsExprs ops otherExprs
diff --git a/src/Ormolu/Printer/Meat/Declaration/OpTree.hs-boot b/src/Ormolu/Printer/Meat/Declaration/OpTree.hs-boot
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Printer/Meat/Declaration/OpTree.hs-boot
@@ -0,0 +1,33 @@
+module Ormolu.Printer.Meat.Declaration.OpTree
+  ( p_exprOpTree,
+    exprOpTree,
+    p_cmdOpTree,
+    cmdOpTree,
+    p_tyOpTree,
+    tyOpTree,
+    getOpName,
+    getOpNameStr,
+  )
+where
+
+import GHC.Hs
+import GHC.Types.Name.Reader (RdrName)
+import Ormolu.Printer.Combinators (BracketStyle, R)
+import Ormolu.Printer.Operators (OpInfo, OpTree)
+
+exprOpTree :: LHsExpr GhcPs -> OpTree (LHsExpr GhcPs) (LHsExpr GhcPs)
+p_exprOpTree ::
+  BracketStyle ->
+  OpTree (LHsExpr GhcPs) (OpInfo (LHsExpr GhcPs)) ->
+  R ()
+cmdOpTree :: LHsCmdTop GhcPs -> OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs)
+p_cmdOpTree ::
+  BracketStyle ->
+  OpTree (LHsCmdTop GhcPs) (OpInfo (LHsExpr GhcPs)) ->
+  R ()
+tyOpTree :: LHsType GhcPs -> OpTree (LHsType GhcPs) (LocatedN RdrName)
+p_tyOpTree ::
+  OpTree (LHsType GhcPs) (OpInfo (LocatedN RdrName)) ->
+  R ()
+getOpName :: HsExpr GhcPs -> Maybe RdrName
+getOpNameStr :: RdrName -> String
diff --git a/src/Ormolu/Printer/Meat/Declaration/RoleAnnotation.hs b/src/Ormolu/Printer/Meat/Declaration/RoleAnnotation.hs
--- a/src/Ormolu/Printer/Meat/Declaration/RoleAnnotation.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/RoleAnnotation.hs
@@ -9,17 +9,15 @@
 where
 
 import GHC.Core.Coercion.Axiom
-import GHC.Hs.Decls
-import GHC.Hs.Extension
+import GHC.Hs hiding (anns)
 import GHC.Types.Name.Reader
-import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 
 p_roleAnnot :: RoleAnnotDecl GhcPs -> R ()
-p_roleAnnot (RoleAnnotDecl NoExtField l_name anns) = p_roleAnnot' l_name anns
+p_roleAnnot (RoleAnnotDecl _ l_name anns) = p_roleAnnot' l_name anns
 
-p_roleAnnot' :: Located RdrName -> [Located (Maybe Role)] -> R ()
+p_roleAnnot' :: LocatedN RdrName -> [XRec GhcPs (Maybe Role)] -> R ()
 p_roleAnnot' l_name anns = do
   txt "type role"
   breakpoint
diff --git a/src/Ormolu/Printer/Meat/Declaration/Rule.hs b/src/Ormolu/Printer/Meat/Declaration/Rule.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Rule.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Rule.hs
@@ -4,41 +4,30 @@
 
 module Ormolu.Printer.Meat.Declaration.Rule
   ( p_ruleDecls,
+    p_ruleBndrs,
   )
 where
 
-import Control.Monad (unless)
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Lit
-import GHC.Hs.Type
+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
 
 p_ruleDecls :: RuleDecls GhcPs -> R ()
-p_ruleDecls (HsRules NoExtField _ xs) =
+p_ruleDecls (HsRules _ xs) =
   pragma "RULES" $ sep breakpoint (sitcc . located' p_ruleDecl) xs
 
 p_ruleDecl :: RuleDecl GhcPs -> R ()
-p_ruleDecl (HsRule NoExtField 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
@@ -48,12 +37,23 @@
       breakpoint
       located rhs p_hsExpr
 
-p_ruleName :: (SourceText, RuleName) -> R ()
-p_ruleName (_, name) = atom $ (HsString NoSourceText name :: HsLit GhcPs)
+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
-  RuleBndr NoExtField x -> p_rdrName x
-  RuleBndrSig NoExtField x HsPS {..} -> parens N $ do
+  RuleBndr _ x -> p_rdrName x
+  RuleBndrSig _ x HsPS {..} -> parens N $ do
     p_rdrName x
-    p_typeAscription (HsWC NoExtField (HsIB NoExtField hsps_body))
+    p_typeAscription (lhsTypeToSigType hsps_body)
diff --git a/src/Ormolu/Printer/Meat/Declaration/Signature.hs b/src/Ormolu/Printer/Meat/Declaration/Signature.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Signature.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Signature.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
 
 -- | Type signature declarations.
 module Ormolu.Printer.Meat.Declaration.Signature
@@ -8,78 +9,78 @@
     p_typeAscription,
     p_activation,
     p_standaloneKindSig,
+    deconstructExprFromSpecSigE,
   )
 where
 
 import Control.Monad
+import Data.Maybe (maybeToList)
 import GHC.Data.BooleanFormula
-import GHC.Hs.Binds
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
+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
 
 p_sigDecl :: Sig GhcPs -> R ()
 p_sigDecl = \case
-  TypeSig NoExtField names hswc -> p_typeSig True names hswc
-  PatSynSig NoExtField names hsib -> p_patSynSig names hsib
-  ClassOpSig NoExtField def names hsib -> p_classOpSig def names hsib
-  FixSig NoExtField sig -> p_fixSig sig
-  InlineSig NoExtField name inlinePragma -> p_inlineSig name inlinePragma
-  SpecSig NoExtField name ts inlinePragma -> p_specSig name ts inlinePragma
-  SpecInstSig NoExtField _ hsib -> p_specInstSig hsib
-  MinimalSig NoExtField _ booleanFormula -> p_minimalSig booleanFormula
-  CompleteMatchSig NoExtField _sourceText cs ty -> p_completeSig cs ty
-  SCCFunSig NoExtField _ name literal -> p_sccSig name literal
-  _ -> notImplemented "certain types of signature declarations"
+  TypeSig _ names hswc -> p_typeSig True names (hswc_body hswc)
+  PatSynSig _ names sigType -> p_patSynSig names sigType
+  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 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
+  SCCFunSig _ name literal -> p_sccSig name literal
 
 p_typeSig ::
   -- | Should the tail of the names be indented
   Bool ->
   -- | Names (before @::@)
-  [Located RdrName] ->
+  [LocatedN RdrName] ->
   -- | Type
-  LHsSigWcType GhcPs ->
+  LHsSigType GhcPs ->
   R ()
 p_typeSig _ [] _ = return () -- should not happen though
-p_typeSig indentTail (n : ns) hswc = do
+p_typeSig indentTail (n : ns) sigType = do
   p_rdrName n
   if null ns
-    then p_typeAscription hswc
+    then p_typeAscription sigType
     else inciIf indentTail $ do
       commaDel
       sep commaDel p_rdrName ns
-      p_typeAscription hswc
+      p_typeAscription sigType
 
 p_typeAscription ::
-  LHsSigWcType GhcPs ->
+  LHsSigType GhcPs ->
   R ()
-p_typeAscription HsWC {..} = inci $ do
+p_typeAscription sigType = inci $ do
   space
   txt "::"
-  let t = hsib_body hswc_body
-  if hasDocStrings (unLoc t)
+  if hasDocStrings (unLoc . sig_body . unLoc $ sigType)
     then newline
     else breakpoint
-  located t p_hsType
+  located sigType p_hsSigType
 
 p_patSynSig ::
-  [Located RdrName] ->
-  HsImplicitBndrs GhcPs (LHsType GhcPs) ->
+  [LocatedN RdrName] ->
+  LHsSigType GhcPs ->
   R ()
-p_patSynSig names hsib = do
+p_patSynSig names sigType = do
   txt "pattern"
-  let body =
-        p_typeSig
-          False
-          names
-          HsWC {hswc_ext = NoExtField, hswc_body = hsib}
+  let body = p_typeSig False names sigType
   if length names > 1
     then breakpoint >> inci body
     else space >> body
@@ -88,19 +89,19 @@
   -- | Whether this is a \"default\" signature
   Bool ->
   -- | Names (before @::@)
-  [Located RdrName] ->
+  [LocatedN RdrName] ->
   -- | Type
-  HsImplicitBndrs GhcPs (LHsType GhcPs) ->
+  LHsSigType GhcPs ->
   R ()
-p_classOpSig def names hsib = do
+p_classOpSig def names sigType = do
   when def (txt "default" >> space)
-  p_typeSig True names HsWC {hswc_ext = NoExtField, hswc_body = hsib}
+  p_typeSig True names sigType
 
 p_fixSig ::
   FixitySig GhcPs ->
   R ()
 p_fixSig = \case
-  FixitySig NoExtField names (Fixity _ n dir) -> do
+  FixitySig namespace names (Fixity n dir) -> do
     txt $ case dir of
       InfixL -> "infixl"
       InfixR -> "infixr"
@@ -108,11 +109,12 @@
     space
     atom n
     space
+    p_namespaceSpec namespace
     sitcc $ sep commaDel p_rdrName names
 
 p_inlineSig ::
   -- | Name
-  Located RdrName ->
+  LocatedN RdrName ->
   -- | Inline pragma specification
   InlinePragma ->
   R ()
@@ -123,41 +125,87 @@
     ConLike -> txt "CONLIKE"
     FunLike -> return ()
   space
-  p_activation inl_act
+  when (inl_act /= NeverActive) $ p_activation inl_act
   space
   p_rdrName name
 
 p_specSig ::
-  -- | Name
-  Located 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
-  txt "::"
-  breakpoint
-  inci $ sep commaDel (located' p_hsType . hsib_body) 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
+        txt "::"
+        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"
-  Inlinable -> txt "INLINEABLE"
-  NoInline -> txt "NOINLINE"
-  NoUserInline -> return ()
+  Inline _ -> txt "INLINE"
+  Inlinable _ -> txt "INLINEABLE"
+  NoInline _ -> txt "NOINLINE"
+  Opaque _ -> txt "OPAQUE"
+  NoUserInlinePrag -> return ()
 
 p_activation :: Activation -> R ()
 p_activation = \case
-  NeverActive -> return ()
+  NeverActive -> txt "[~]"
   AlwaysActive -> return ()
   ActiveBefore _ n -> do
     txt "[~"
@@ -170,13 +218,13 @@
   FinalActive -> notImplemented "FinalActive" -- NOTE(amesgen) is this unreachable or just not implemented?
 
 p_specInstSig :: LHsSigType GhcPs -> R ()
-p_specInstSig hsib =
+p_specInstSig sigType =
   pragma "SPECIALIZE instance" . inci $
-    located (hsib_body hsib) p_hsType
+    located sigType p_hsSigType
 
 p_minimalSig ::
   -- | Boolean formula
-  LBooleanFormula (Located RdrName) ->
+  LBooleanFormula GhcPs ->
   R ()
 p_minimalSig =
   located' $ \booleanFormula ->
@@ -184,7 +232,7 @@
 
 p_booleanFormula ::
   -- | Boolean formula
-  BooleanFormula (Located RdrName) ->
+  BooleanFormula GhcPs ->
   R ()
 p_booleanFormula = \case
   Var name -> p_rdrName name
@@ -204,12 +252,12 @@
 
 p_completeSig ::
   -- | Constructors\/patterns
-  Located [Located RdrName] ->
+  [LIdP GhcPs] ->
   -- | Type
-  Maybe (Located RdrName) ->
+  Maybe (LocatedN RdrName) ->
   R ()
-p_completeSig cs' mty =
-  located cs' $ \cs ->
+p_completeSig cs mty =
+  switchLayout (getLocA <$> cs) $
     pragma "COMPLETE" . inci $ do
       sep commaDel p_rdrName cs
       forM_ mty $ \ty -> do
@@ -218,7 +266,7 @@
         breakpoint
         inci (p_rdrName ty)
 
-p_sccSig :: Located (IdP GhcPs) -> Maybe (Located StringLiteral) -> R ()
+p_sccSig :: LocatedN RdrName -> Maybe (XRec GhcPs StringLiteral) -> R ()
 p_sccSig loc literal = pragma "SCC" . inci $ do
   p_rdrName loc
   forM_ literal $ \x -> do
@@ -226,7 +274,7 @@
     atom x
 
 p_standaloneKindSig :: StandaloneKindSig GhcPs -> R ()
-p_standaloneKindSig (StandaloneKindSig NoExtField name (HsIB NoExtField sig)) = do
+p_standaloneKindSig (StandaloneKindSig _ name sigTy) = do
   txt "type"
   inci $ do
     space
@@ -234,4 +282,4 @@
     space
     txt "::"
     breakpoint
-    located sig p_hsType
+    located sigTy p_hsSigType
diff --git a/src/Ormolu/Printer/Meat/Declaration/Signature.hs-boot b/src/Ormolu/Printer/Meat/Declaration/Signature.hs-boot
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Printer/Meat/Declaration/Signature.hs-boot
@@ -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 ()
diff --git a/src/Ormolu/Printer/Meat/Declaration/Splice.hs b/src/Ormolu/Printer/Meat/Declaration/Splice.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Splice.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Splice.hs
@@ -1,15 +1,12 @@
-{-# LANGUAGE LambdaCase #-}
-
 module Ormolu.Printer.Meat.Declaration.Splice
   ( p_spliceDecl,
   )
 where
 
-import GHC.Hs.Decls
-import GHC.Hs.Extension
+import GHC.Hs
 import Ormolu.Printer.Combinators
-import Ormolu.Printer.Meat.Declaration.Value (p_hsSplice)
+import Ormolu.Printer.Meat.Declaration.Value (p_hsUntypedSplice)
 
 p_spliceDecl :: SpliceDecl GhcPs -> R ()
-p_spliceDecl = \case
-  SpliceDecl NoExtField splice _explicit -> located splice p_hsSplice
+p_spliceDecl (SpliceDecl NoExtField splice deco) =
+  located splice $ p_hsUntypedSplice deco
diff --git a/src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs b/src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Printer/Meat/Declaration/StringLiteral.hs
@@ -0,0 +1,174 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Ormolu.Printer.Meat.Declaration.StringLiteral (p_stringLit) where
+
+import Control.Applicative (Alternative (..))
+import Control.Category ((>>>))
+import Control.Monad ((>=>))
+import Data.Semigroup (Min (..))
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Data.FastString
+import GHC.Parser.CharClass (is_space)
+import Ormolu.Printer.Combinators
+import Ormolu.Utils
+
+-- | 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
+  Just ParsedStringLiteral {..} -> sitcc do
+    txt startMarker
+    case stringLiteralKind of
+      RegularStringLiteral -> do
+        let singleLine =
+              txt $ intercalateMinimalStringGaps segments
+            multiLine =
+              sep breakpoint f (attachRelativePos segments)
+              where
+                f :: (RelativePos, Text) -> R ()
+                f (pos, s) = case pos of
+                  SinglePos -> txt s
+                  FirstPos -> txt s *> txt "\\"
+                  MiddlePos -> txt "\\" *> txt s *> txt "\\"
+                  LastPos -> txt "\\" *> txt s
+        vlayout singleLine multiLine
+      MultilineStringLiteral ->
+        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).
+data ParsedStringLiteral = ParsedStringLiteral
+  { startMarker, endMarker :: Text,
+    stringLiteralKind :: StringLiteralKind,
+    segments :: [Text]
+  }
+  deriving stock (Show, Eq)
+
+-- | A regular or a multiline string literal.
+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.
+parseStringLiteral :: Text -> Maybe ParsedStringLiteral
+parseStringLiteral = \s -> do
+  psl <-
+    (stripStartEndMarker MultilineStringLiteral "\"\"\"" s)
+      <|> (stripStartEndMarker RegularStringLiteral "\"" s)
+  let splitSegments = case stringLiteralKind psl of
+        RegularStringLiteral -> splitGaps
+        MultilineStringLiteral -> splitMultilineString
+  pure psl {segments = concatMap splitSegments $ segments psl}
+  where
+    -- Remove the given marker from the start and the end (at the end,
+    -- optionally also remove a #).
+    stripStartEndMarker ::
+      StringLiteralKind -> Text -> Text -> Maybe ParsedStringLiteral
+    stripStartEndMarker stringLiteralKind marker s = do
+      let startMarker = marker
+      suffix <- T.stripPrefix startMarker s
+      let markerWithHash = marker <> "#"
+      (endMarker, infix_) <-
+        ((markerWithHash,) <$> T.stripSuffix markerWithHash suffix)
+          <|> ((marker,) <$> T.stripSuffix marker suffix)
+      pure ParsedStringLiteral {segments = [infix_], ..}
+
+    -- Split a string on gaps (backslash delimited whitespaces).
+    --
+    -- > splitGaps "bar\\  \\fo\\&o" == ["bar", "fo\\&o"]
+    splitGaps :: Text -> [Text]
+    splitGaps s = go $ T.breakOnAll "\\" s
+      where
+        go [] = [s]
+        go ((pre, suf) : bs) = case T.uncons suf of
+          Just ('\\', suf')
+            | (gap, T.uncons -> Just ('\\', rest)) <- T.span is_space suf',
+              -- If there is a space after the backslash, this definitely is a
+              -- string gap. Continue parsing gaps after the next backslash.
+              not $ T.null gap ->
+                pre : splitGaps rest
+            | otherwise ->
+                -- Check whether @suf@ starts with an escape sequence involving
+                -- another backslash. If so, it can not 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
+
+        -- 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 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
+        -- emulate multi-line strings, so we replace them with "\\ \\".
+        >>> intercalateMinimalStringGaps
+        >>> splitNewlines
+        >>> fmap expandLeadingTabs
+        >>> rmCommonWhitespacePrefixAndBlank
+
+    -- See the definition of newlines on
+    -- <https://www.haskell.org/onlinereport/haskell2010/haskellch10.html#x17-17800010.3>.
+    splitNewlines :: Text -> [Text]
+    splitNewlines = T.splitOn "\r\n" >=> T.split isNewlineish
+      where
+        isNewlineish c = c == '\n' || c == '\r' || c == '\f'
+
+    -- See GHC's 'lexMultilineString'.
+    expandLeadingTabs :: Text -> Text
+    expandLeadingTabs = T.concat . go 0
+      where
+        go :: Int -> Text -> [Text]
+        go col s = case T.breakOn "\t" s of
+          (pre, T.uncons -> Just (_, suf)) ->
+            let col' = col + T.length pre
+                fill = 8 - (col' `mod` 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
+    -- empty lines.
+    rmCommonWhitespacePrefixAndBlank :: [Text] -> [Text]
+    rmCommonWhitespacePrefixAndBlank = \case
+      [] -> []
+      hd : tl -> hd : tl'
+        where
+          (leadingSpaces, tl') = unzip $ countLeadingAndBlank <$> tl
+
+          commonWs :: Int
+          commonWs = maybe 0 getMin $ mconcat leadingSpaces
+
+          countLeadingAndBlank :: Text -> (Maybe (Min Int), Text)
+          countLeadingAndBlank l
+            | T.all is_space l = (Nothing, "")
+            | otherwise = (Just $ Min leadingSpace, T.drop commonWs l)
+            where
+              leadingSpace = T.length $ T.takeWhile is_space l
+
+-- | Add minimal string gaps between string literal chunks. Such string gaps
+-- /can/ be semantically meaningful (so we preserve them for simplicity); for
+-- example:
+--
+-- >>> "\65\ \0" == "\650"
+-- False
+intercalateMinimalStringGaps :: [Text] -> Text
+intercalateMinimalStringGaps = T.intercalate "\\ \\"
diff --git a/src/Ormolu/Printer/Meat/Declaration/Type.hs b/src/Ormolu/Printer/Meat/Declaration/Type.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Type.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Type.hs
@@ -9,7 +9,8 @@
 
 import GHC.Hs.Extension
 import GHC.Hs.Type
-import GHC.Types.Basic
+import GHC.Parser.Annotation
+import GHC.Types.Fixity
 import GHC.Types.Name.Reader
 import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
@@ -18,7 +19,7 @@
 
 p_synDecl ::
   -- | Type constructor
-  Located RdrName ->
+  LocatedN RdrName ->
   -- | Fixity
   LexicalFixity ->
   -- | Type variables
@@ -29,15 +30,16 @@
 p_synDecl name fixity HsQTvs {..} t = do
   txt "type"
   space
-  switchLayout (getLoc name : map getLoc hsq_explicit) $
+  switchLayout (getLocA name : map getLocA hsq_explicit) $
     p_infixDefHelper
       (case fixity of Infix -> True; _ -> False)
       True
       (p_rdrName name)
       (map (located' p_hsTyVarBndr) hsq_explicit)
-  space
-  equals
-  if hasDocStrings (unLoc t)
-    then newline
-    else breakpoint
-  inci (located t p_hsType)
+  inci $ do
+    space
+    equals
+    if hasDocStrings (unLoc t)
+      then newline
+      else breakpoint
+    located t p_hsType
diff --git a/src/Ormolu/Printer/Meat/Declaration/TypeFamily.hs b/src/Ormolu/Printer/Meat/Declaration/TypeFamily.hs
--- a/src/Ormolu/Printer/Meat/Declaration/TypeFamily.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/TypeFamily.hs
@@ -11,10 +11,8 @@
 
 import Control.Monad
 import Data.Maybe (isNothing)
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
-import GHC.Types.Basic
+import GHC.Hs
+import GHC.Types.Fixity
 import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
@@ -29,9 +27,11 @@
   txt $ case style of
     Associated -> mempty
     Free -> " family"
-  breakpoint
-  inci $ do
-    switchLayout (getLoc fdLName : (getLoc <$> hsq_explicit)) $
+  let headerSpns = getLocA fdLName : (getLocA <$> hsq_explicit)
+      headerAndSigSpns = getLocA fdResultSig : headerSpns
+  inci . switchLayout headerAndSigSpns $ do
+    breakpoint
+    switchLayout headerSpns $ do
       p_infixDefHelper
         (isInfix fdFixity)
         True
@@ -46,7 +46,7 @@
   case mmeqs of
     Nothing -> return ()
     Just meqs -> do
-      inci $ do
+      inci . switchLayout headerAndSigSpns $ do
         breakpoint
         txt "where"
       case meqs of
@@ -55,10 +55,10 @@
           txt ".."
         Just eqs -> do
           newline
-          sep newline (located' (inci . p_tyFamInstEqn)) eqs
+          inci (sep newline (located' p_tyFamInstEqn) eqs)
 
 p_familyResultSigL ::
-  Located (FamilyResultSig GhcPs) ->
+  LFamilyResultSig GhcPs ->
   Maybe (R ())
 p_familyResultSigL (L _ a) = case a of
   NoSig NoExtField -> Nothing
@@ -72,7 +72,7 @@
     located bndr p_hsTyVarBndr
 
 p_injectivityAnn :: InjectivityAnn GhcPs -> R ()
-p_injectivityAnn (InjectivityAnn a bs) = do
+p_injectivityAnn (InjectivityAnn _ a bs) = do
   txt "|"
   space
   p_rdrName a
@@ -82,24 +82,28 @@
   sep space p_rdrName bs
 
 p_tyFamInstEqn :: TyFamInstEqn GhcPs -> R ()
-p_tyFamInstEqn HsIB {hsib_body = FamEqn {..}} = do
+p_tyFamInstEqn FamEqn {..} = do
   case feqn_bndrs of
-    Nothing -> return ()
-    Just bndrs -> do
+    HsOuterImplicit NoExtField -> return ()
+    HsOuterExplicit _ bndrs -> do
       p_forallBndrs ForAllInvis p_hsTyVarBndr bndrs
       breakpoint
-  inciIf (not $ null feqn_bndrs) $ do
-    let famLhsSpn = getLoc feqn_tycon : fmap lhsTypeArgSrcSpan feqn_pats
+  let atLeastOneBndr = case feqn_bndrs of
+        HsOuterImplicit NoExtField -> False
+        HsOuterExplicit _ bndrs -> not $ null bndrs
+  inciIf atLeastOneBndr $ do
+    let famLhsSpn = getLocA feqn_tycon : fmap lhsTypeArgSrcSpan feqn_pats
     switchLayout famLhsSpn $
       p_infixDefHelper
         (isInfix feqn_fixity)
         True
         (p_rdrName feqn_tycon)
         (p_lhsTypeArg <$> feqn_pats)
-    space
-    equals
-    breakpoint
-    inci (located feqn_rhs p_hsType)
+    inci $ do
+      space
+      equals
+      breakpoint
+      located feqn_rhs p_hsType
 
 ----------------------------------------------------------------------------
 -- Helpers
diff --git a/src/Ormolu/Printer/Meat/Declaration/Value.hs b/src/Ormolu/Printer/Meat/Declaration/Value.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Value.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Value.hs
@@ -1,1389 +1,1416 @@
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiWayIf #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE TypeApplications #-}
-{-# LANGUAGE ViewPatterns #-}
-
-module Ormolu.Printer.Meat.Declaration.Value
-  ( p_valDecl,
-    p_pat,
-    p_hsExpr,
-    p_hsSplice,
-    p_stringLit,
-  )
-where
-
-import Control.Monad
-import Data.Bool (bool)
-import Data.Char (isPunctuation, isSymbol)
-import Data.Data hiding (Infix, Prefix)
-import Data.Function (on)
-import Data.Functor ((<&>))
-import Data.Generics.Schemes (everything)
-import Data.List (intersperse, sortBy)
-import Data.List.NonEmpty (NonEmpty (..), (<|))
-import qualified Data.List.NonEmpty as NE
-import Data.Maybe (isJust)
-import Data.Text (Text)
-import qualified Data.Text as Text
-import GHC.Data.Bag (bagToList)
-import GHC.Hs.Binds
-import GHC.Hs.Expr
-import GHC.Hs.Extension
-import GHC.Hs.Lit
-import GHC.Hs.Pat
-import GHC.Hs.Type
-import GHC.LanguageExtensions.Type (Extension (NegativeLiterals))
-import GHC.Parser.Annotation
-import GHC.Parser.CharClass (is_space)
-import GHC.Types.Basic
-import GHC.Types.Name.Occurrence (occNameString)
-import GHC.Types.Name.Reader
-import GHC.Types.SrcLoc
-import Ormolu.Printer.Combinators
-import Ormolu.Printer.Internal
-import Ormolu.Printer.Meat.Common
-import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration
-import Ormolu.Printer.Meat.Declaration.Signature
-import Ormolu.Printer.Meat.Type
-import Ormolu.Printer.Operators
-import Ormolu.Utils
-
--- | Style of a group of equations.
-data MatchGroupStyle
-  = Function (Located RdrName)
-  | PatternBind
-  | Case
-  | Lambda
-  | LambdaCase
-
--- | Style of equations in a group.
-data GroupStyle
-  = EqualSign
-  | RightArrow
-
--- | Expression placement. This marks the places where expressions that
--- implement handing forms may use them.
-data Placement
-  = -- | Multi-line layout should cause
-    -- insertion of a newline and indentation
-    -- bump
-    Normal
-  | -- | Expressions that have hanging form
-    -- should use it and avoid bumping one level
-    -- of indentation
-    Hanging
-  deriving (Eq, Show)
-
-p_valDecl :: HsBindLR GhcPs GhcPs -> R ()
-p_valDecl = \case
-  FunBind NoExtField funId funMatches _ -> p_funBind funId funMatches
-  PatBind NoExtField pat grhss _ -> p_match PatternBind False NoSrcStrict [pat] grhss
-  VarBind {} -> notImplemented "VarBinds" -- introduced by the type checker
-  AbsBinds {} -> notImplemented "AbsBinds" -- introduced by the type checker
-  PatSynBind NoExtField psb -> p_patSynBind psb
-
-p_funBind ::
-  Located RdrName ->
-  MatchGroup GhcPs (LHsExpr GhcPs) ->
-  R ()
-p_funBind name = p_matchGroup (Function name)
-
-p_matchGroup ::
-  MatchGroupStyle ->
-  MatchGroup GhcPs (LHsExpr GhcPs) ->
-  R ()
-p_matchGroup = p_matchGroup' exprPlacement p_hsExpr
-
-p_matchGroup' ::
-  Data body =>
-  -- | How to get body placement
-  (body -> Placement) ->
-  -- | How to print body
-  (body -> R ()) ->
-  -- | Style of this group of equations
-  MatchGroupStyle ->
-  -- | Match group
-  MatchGroup GhcPs (Located body) ->
-  R ()
-p_matchGroup' placer render style MG {..} = do
-  let ob = case style of
-        Case -> id
-        LambdaCase -> id
-        _ -> dontUseBraces
-  -- 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
-  ob $ sepSemi (located' (ub . p_Match)) (unLoc mg_alts)
-  where
-    p_Match m@Match {..} =
-      p_match'
-        placer
-        render
-        (adjustMatchGroupStyle m style)
-        (isInfixMatch m)
-        (matchStrictness m)
-        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.
-adjustMatchGroupStyle ::
-  Match GhcPs body ->
-  MatchGroupStyle ->
-  MatchGroupStyle
-adjustMatchGroupStyle m = \case
-  Function _ -> (Function . mc_fun . m_ctxt) m
-  style -> style
-
-matchStrictness :: Match id body -> SrcStrictness
-matchStrictness match =
-  case m_ctxt match of
-    FunRhs {mc_strictness = s} -> s
-    _ -> NoSrcStrict
-
-p_match ::
-  -- | Style of the group
-  MatchGroupStyle ->
-  -- | Is this an infix match?
-  Bool ->
-  -- | Strictness prefix (FunBind)
-  SrcStrictness ->
-  -- | Argument patterns
-  [LPat GhcPs] ->
-  -- | Equations
-  GRHSs GhcPs (LHsExpr GhcPs) ->
-  R ()
-p_match = p_match' exprPlacement p_hsExpr
-
-p_match' ::
-  Data body =>
-  -- | How to get body placement
-  (body -> Placement) ->
-  -- | How to print body
-  (body -> R ()) ->
-  -- | Style of this group of equations
-  MatchGroupStyle ->
-  -- | Is this an infix match?
-  Bool ->
-  -- | Strictness prefix (FunBind)
-  SrcStrictness ->
-  -- | Argument patterns
-  [LPat GhcPs] ->
-  -- | Equations
-  GRHSs GhcPs (Located body) ->
-  R ()
-p_match' placer render style isInfix 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
-  -- 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.
-  case strictness of
-    NoSrcStrict -> return ()
-    SrcStrict -> txt "!"
-    SrcLazy -> txt "~"
-  indentBody <- case NE.nonEmpty m_pats of
-    Nothing ->
-      False <$ case style of
-        Function name -> p_rdrName name
-        _ -> return ()
-    Just ne_pats -> do
-      let combinedSpans = case style of
-            Function name -> combineSrcSpans (getLoc name) patSpans
-            _ -> patSpans
-          patSpans = combineSrcSpans' (getLoc <$> ne_pats)
-          indentBody = not (isOneLineSpan combinedSpans)
-      switchLayout [combinedSpans] $ do
-        let stdCase = sep breakpoint (located' p_pat) m_pats
-        case style of
-          Function name ->
-            p_infixDefHelper
-              isInfix
-              indentBody
-              (p_rdrName name)
-              (located' p_pat <$> m_pats)
-          PatternBind -> stdCase
-          Case -> stdCase
-          Lambda -> do
-            let needsSpace = case unLoc (NE.head ne_pats) of
-                  LazyPat _ _ -> True
-                  BangPat _ _ -> True
-                  SplicePat _ _ -> True
-                  _ -> False
-            txt "\\"
-            when needsSpace space
-            sitcc stdCase
-          LambdaCase -> stdCase
-      return indentBody
-  let -- Calculate position of end of patterns. This is useful when we decide
-      -- about putting certain constructions in hanging positions.
-      endOfPats = case NE.nonEmpty m_pats of
-        Nothing -> case style of
-          Function name -> Just (getLoc name)
-          _ -> Nothing
-        Just pats -> (Just . getLoc . NE.last) pats
-      isCase = \case
-        Case -> True
-        LambdaCase -> True
-        _ -> False
-      hasGuards = withGuards grhssGRHSs
-      grhssSpan =
-        combineSrcSpans' $
-          getGRHSSpan . unLoc <$> NE.fromList grhssGRHSs
-      patGrhssSpan =
-        maybe
-          grhssSpan
-          (combineSrcSpans grhssSpan . srcLocSpan . srcSpanEnd)
-          endOfPats
-      placement =
-        case endOfPats of
-          Nothing -> blockPlacement placer grhssGRHSs
-          Just spn ->
-            if onTheSameLine spn grhssSpan
-              then blockPlacement placer grhssGRHSs
-              else Normal
-      p_body = do
-        let groupStyle =
-              if isCase style && hasGuards
-                then RightArrow
-                else EqualSign
-        sep newline (located' (p_grhs' placer render groupStyle)) grhssGRHSs
-      p_where = do
-        let whereIsEmpty = eqEmptyLocalBinds (unLoc grhssLocalBinds)
-        unless (eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do
-          breakpoint
-          txt "where"
-          unless whereIsEmpty breakpoint
-          inci $ located grhssLocalBinds p_hsLocalBinds
-  inciIf indentBody $ do
-    unless (length grhssGRHSs > 1) $
-      case style of
-        Function _ | hasGuards -> return ()
-        Function _ -> space >> inci equals
-        PatternBind -> space >> inci equals
-        s | isCase s && hasGuards -> return ()
-        _ -> space >> txt "->"
-    switchLayout [patGrhssSpan] $
-      placeHanging placement p_body
-    inci p_where
-
-p_grhs :: GroupStyle -> GRHS GhcPs (LHsExpr GhcPs) -> R ()
-p_grhs = p_grhs' exprPlacement p_hsExpr
-
-p_grhs' ::
-  Data body =>
-  -- | How to get body placement
-  (body -> Placement) ->
-  -- | How to print body
-  (body -> R ()) ->
-  GroupStyle ->
-  GRHS GhcPs (Located body) ->
-  R ()
-p_grhs' placer render style (GRHS NoExtField guards body) =
-  case guards of
-    [] -> p_body
-    xs -> do
-      txt "|"
-      space
-      sitcc (sep commaDel (sitcc . located' p_stmt) xs)
-      space
-      inci $ case style of
-        EqualSign -> equals
-        RightArrow -> txt "->"
-      placeHanging placement p_body
-  where
-    placement =
-      case endOfGuards of
-        Nothing -> placer (unLoc body)
-        Just spn ->
-          if onTheSameLine spn (getLoc body)
-            then placer (unLoc body)
-            else Normal
-    endOfGuards =
-      case NE.nonEmpty guards of
-        Nothing -> Nothing
-        Just gs -> (Just . getLoc . NE.last) gs
-    p_body = located body render
-
-p_hsCmd :: HsCmd GhcPs -> R ()
-p_hsCmd = p_hsCmd' N
-
-p_hsCmd' :: BracketStyle -> HsCmd GhcPs -> R ()
-p_hsCmd' s = \case
-  HsCmdArrApp NoExtField body input arrType rightToLeft -> do
-    let (l, r) = if rightToLeft then (body, input) else (input, body)
-    located l p_hsExpr
-    breakpoint
-    inci $ do
-      case (arrType, rightToLeft) of
-        (HsFirstOrderApp, True) -> txt "-<"
-        (HsHigherOrderApp, True) -> txt "-<<"
-        (HsFirstOrderApp, False) -> txt ">-"
-        (HsHigherOrderApp, False) -> txt ">>-"
-      placeHanging (exprPlacement (unLoc input)) $
-        located r p_hsExpr
-  HsCmdArrForm NoExtField form Prefix _ cmds -> banana s $ do
-    located form p_hsExpr
-    unless (null cmds) $ do
-      breakpoint
-      inci (sequence_ (intersperse breakpoint (located' p_hsCmdTop <$> cmds)))
-  HsCmdArrForm NoExtField form Infix _ [left, right] -> do
-    located left p_hsCmdTop
-    case unLoc left of
-      HsCmdTop NoExtField (L _ HsCmdPar {}) -> space
-      _ -> breakpoint
-    located form p_hsExpr
-    placeHanging (cmdTopPlacement (unLoc right)) $
-      located right p_hsCmdTop
-  HsCmdArrForm NoExtField _ Infix _ _ -> notImplemented "HsCmdArrForm"
-  HsCmdApp NoExtField cmd expr -> do
-    located cmd (p_hsCmd' s)
-    space
-    located expr p_hsExpr
-  HsCmdLam NoExtField mgroup -> p_matchGroup' cmdPlacement p_hsCmd Lambda mgroup
-  HsCmdPar NoExtField c -> parens N (located c p_hsCmd)
-  HsCmdCase NoExtField e mgroup ->
-    p_case cmdPlacement p_hsCmd e mgroup
-  HsCmdLamCase NoExtField mgroup ->
-    p_lamcase cmdPlacement p_hsCmd mgroup
-  HsCmdIf NoExtField _ if' then' else' ->
-    p_if cmdPlacement p_hsCmd if' then' else'
-  HsCmdLet NoExtField localBinds c ->
-    p_let p_hsCmd localBinds c
-  HsCmdDo NoExtField es -> do
-    txt "do"
-    newline
-    inci . located es $
-      sitcc . sep newline (sitcc . withSpacing (p_stmt' cmdPlacement (p_hsCmd' S)))
-
-p_hsCmdTop :: HsCmdTop GhcPs -> R ()
-p_hsCmdTop (HsCmdTop NoExtField cmd) = located cmd p_hsCmd
-
--- | Render an expression preserving blank lines between such consecutive
--- expressions found in the original source code.
-withSpacing ::
-  -- | Rendering function
-  (a -> R ()) ->
-  -- | Entity to render
-  Located a ->
-  R ()
-withSpacing f l = located l $ \x -> do
-  case getLoc 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 ()
-      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)
-
-p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R ()
-p_stmt = p_stmt' exprPlacement p_hsExpr
-
-p_stmt' ::
-  Data body =>
-  -- | Placer
-  (body -> Placement) ->
-  -- | Render
-  (body -> R ()) ->
-  -- | Statement to render
-  Stmt GhcPs (Located body) ->
-  R ()
-p_stmt' placer render = \case
-  LastStmt NoExtField body _ _ -> located body render
-  BindStmt NoExtField p f@(L l x) -> do
-    located p p_pat
-    space
-    txt "<-"
-    let loc = getLoc p
-        placement
-          | isOneLineSpan (mkSrcSpan (srcSpanEnd loc) (srcSpanStart l)) = placer x
-          | otherwise = Normal
-    switchLayout [loc, l] $
-      placeHanging placement (located f render)
-  ApplicativeStmt {} -> notImplemented "ApplicativeStmt" -- generated by renamer
-  BodyStmt NoExtField body _ _ -> located body render
-  LetStmt NoExtField binds -> do
-    txt "let"
-    space
-    sitcc $ located binds p_hsLocalBinds
-  ParStmt {} ->
-    -- 'ParStmt' should always be eliminated in 'gatherStmt' already, such
-    -- 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
-    -- handled through 'gatherStmt'.
-    case (trS_form, trS_by) of
-      (ThenForm, Nothing) -> do
-        txt "then"
-        breakpoint
-        inci $ located trS_using p_hsExpr
-      (ThenForm, Just e) -> do
-        txt "then"
-        breakpoint
-        inci $ located trS_using p_hsExpr
-        breakpoint
-        txt "by"
-        breakpoint
-        inci $ located e p_hsExpr
-      (GroupForm, Nothing) -> do
-        txt "then group using"
-        breakpoint
-        inci $ located trS_using p_hsExpr
-      (GroupForm, Just e) -> do
-        txt "then group by"
-        breakpoint
-        inci $ located e p_hsExpr
-        breakpoint
-        txt "using"
-        breakpoint
-        inci $ located trS_using p_hsExpr
-  RecStmt {..} -> do
-    txt "rec"
-    space
-    sitcc $ sepSemi (withSpacing (p_stmt' placer render)) recS_stmts
-
-gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]]
-gatherStmt (L _ (ParStmt NoExtField block _ _)) =
-  foldr ((<>) . gatherStmtBlock) [] block
-gatherStmt (L s stmt@TransStmt {..}) =
-  foldr liftAppend [] ((gatherStmt <$> trS_stmts) <> pure [[L s stmt]])
-gatherStmt stmt = [[stmt]]
-
-gatherStmtBlock :: ParStmtBlock GhcPs GhcPs -> [[ExprLStmt GhcPs]]
-gatherStmtBlock (ParStmtBlock _ stmts _ _) =
-  foldr (liftAppend . gatherStmt) [] stmts
-
-p_hsLocalBinds :: HsLocalBindsLR GhcPs GhcPs -> R ()
-p_hsLocalBinds = \case
-  HsValBinds NoExtField (ValBinds NoExtField bag lsigs) -> 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.
-    br <- layoutToBraces <$> getLayout
-    let items =
-          let injectLeft (L l x) = L l (Left x)
-              injectRight (L l x) = L l (Right x)
-           in (injectLeft <$> bagToList bag) ++ (injectRight <$> lsigs)
-        positionToBracing = \case
-          SinglePos -> id
-          FirstPos -> br
-          MiddlePos -> br
-          LastPos -> id
-        p_item' (p, item) =
-          positionToBracing p $
-            withSpacing (either p_valDecl p_sigDecl) item
-        binds = sortBy (leftmost_smallest `on` getLoc) items
-    sitcc $ sepSemi p_item' (attachRelativePos binds)
-  HsValBinds NoExtField _ -> notImplemented "HsValBinds"
-  HsIPBinds NoExtField (IPBinds NoExtField xs) ->
-    -- Second argument of IPBind is always Left before type-checking.
-    let p_ipBind (IPBind NoExtField (Left name) expr) = do
-          atom name
-          space
-          equals
-          breakpoint
-          useBraces $ inci $ located expr p_hsExpr
-        p_ipBind (IPBind NoExtField (Right _) _) =
-          -- Should only occur after the type checker
-          notImplemented "IPBind _ (Right _) _"
-     in sepSemi (located' p_ipBind) xs
-  EmptyLocalBinds NoExtField -> return ()
-
-p_hsRecField ::
-  HsRecField' RdrName (LHsExpr GhcPs) ->
-  R ()
-p_hsRecField HsRecField {..} = do
-  p_rdrName hsRecFieldLbl
-  unless hsRecPun $ do
-    space
-    equals
-    let placement =
-          if onTheSameLine (getLoc hsRecFieldLbl) (getLoc hsRecFieldArg)
-            then exprPlacement (unLoc hsRecFieldArg)
-            else Normal
-    placeHanging placement (located hsRecFieldArg p_hsExpr)
-
-p_hsExpr :: HsExpr GhcPs -> R ()
-p_hsExpr = p_hsExpr' N
-
-p_hsExpr' :: BracketStyle -> HsExpr GhcPs -> R ()
-p_hsExpr' s = \case
-  HsVar NoExtField name -> p_rdrName name
-  HsUnboundVar NoExtField occ -> atom occ
-  HsConLikeOut NoExtField _ -> notImplemented "HsConLikeOut"
-  HsRecFld NoExtField x ->
-    case x of
-      Unambiguous NoExtField name -> p_rdrName name
-      Ambiguous NoExtField name -> p_rdrName name
-  HsOverLabel NoExtField _ v -> do
-    txt "#"
-    atom v
-  HsIPVar NoExtField (HsIPName name) -> do
-    txt "?"
-    atom name
-  HsOverLit NoExtField v -> atom (ol_val v)
-  HsLit NoExtField lit ->
-    case lit of
-      HsString (SourceText stxt) _ -> p_stringLit stxt
-      HsStringPrim (SourceText stxt) _ -> p_stringLit stxt
-      r -> atom r
-  HsLam NoExtField mgroup ->
-    p_matchGroup Lambda mgroup
-  HsLamCase NoExtField mgroup ->
-    p_lamcase exprPlacement p_hsExpr mgroup
-  HsApp NoExtField f x -> do
-    let -- In order to format function applications with multiple parameters
-        -- nicer, traverse the AST to gather the function and all the
-        -- parameters together.
-        gatherArgs f' knownArgs =
-          case f' of
-            L _ (HsApp _ l r) -> gatherArgs l (r <| knownArgs)
-            _ -> (f', knownArgs)
-        (func, args) = gatherArgs f (x :| [])
-        -- We need to handle the last argument specially if it is a
-        -- hanging construct, so separate it from the rest.
-        (initp, lastp) = (NE.init args, NE.last args)
-        initSpan =
-          combineSrcSpans' $
-            getLoc f :| [(srcLocSpan . srcSpanStart . getLoc) lastp]
-        -- Hang the last argument only if the initial arguments span one
-        -- line.
-        placement =
-          if isOneLineSpan initSpan
-            then exprPlacement (unLoc lastp)
-            else 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.
-    case placement of
-      Normal -> do
-        let -- Usually we want to bump indentation for arguments for the
-            -- sake of readability. However:
-            -- When the function is itself a multi line do-block or a case
-            -- expression, we can't indent by indentStep or more.
-            -- When we are on the other hand *in* a do block, we have to
-            -- indent by at least 1.
-            -- Thus, we indent by half of indentStep when the function is
-            -- a multi line do block or case expression.
-            indentArg
-              | isOneLineSpan (getLoc func) = inci
-              | otherwise = case unLoc func of
-                HsDo {} -> inciHalf
-                HsCase {} -> inciHalf
-                HsLamCase {} -> inciHalf
-                _ -> inci
-        ub <-
-          getLayout <&> \case
-            SingleLine -> useBraces
-            MultiLine -> id
-        ub $ do
-          located func (p_hsExpr' s)
-          breakpoint
-          indentArg $ sep breakpoint (located' p_hsExpr) initp
-        indentArg $ do
-          unless (null initp) breakpoint
-          located lastp p_hsExpr
-      Hanging -> do
-        useBraces . switchLayout [initSpan] $ do
-          located func (p_hsExpr' s)
-          breakpoint
-          sep breakpoint (located' p_hsExpr) initp
-        placeHanging placement $
-          located lastp p_hsExpr
-  HsAppType NoExtField e a -> do
-    located e p_hsExpr
-    breakpoint
-    inci $ do
-      txt "@"
-      -- Insert a space when the type is represented as a TH splice to avoid
-      -- gluing @ and $ together.
-      case unLoc (hswc_body a) of
-        HsSpliceTy {} -> space
-        _ -> return ()
-      located (hswc_body a) p_hsType
-  OpApp NoExtField x op y -> do
-    let opTree = OpBranch (exprOpTree x) op (exprOpTree y)
-    p_exprOpTree s (reassociateOpTree getOpName opTree)
-  NegApp NoExtField e NoExtField -> do
-    negativeLiterals <- isExtensionEnabled NegativeLiterals
-    let isLiteral = case unLoc e of
-          HsLit {} -> True
-          HsOverLit {} -> True
-          _ -> False
-    txt "-"
-    -- If NegativeLiterals is enabled, we have to insert a space before
-    -- negated literals, as `- 1` and `-1` have differing AST.
-    when (negativeLiterals && isLiteral) space
-    located e p_hsExpr
-  HsPar NoExtField e ->
-    parens s (located e (dontUseBraces . p_hsExpr))
-  SectionL NoExtField x op -> do
-    located x p_hsExpr
-    breakpoint
-    inci (located op p_hsExpr)
-  SectionR NoExtField op x -> do
-    located op p_hsExpr
-    useRecordDot' <- useRecordDot
-    let isRecordDot' = isRecordDot (unLoc op) (getLoc x)
-    unless (useRecordDot' && isRecordDot') breakpoint
-    inci (located x p_hsExpr)
-  ExplicitTuple NoExtField args boxity ->
-    let isSection = any (isMissing . unLoc) args
-        isMissing = \case
-          Missing NoExtField -> True
-          _ -> False
-        p_arg = \case
-          Present NoExtField x -> located x p_hsExpr
-          Missing NoExtField -> pure ()
-        p_larg = sitcc . located' p_arg
-        parens' =
-          case boxity of
-            Boxed -> parens
-            Unboxed -> parensHash
-     in if isSection
-          then
-            switchLayout [] . parens' s $
-              sep comma p_larg args
-          else
-            switchLayout (getLoc <$> args) . parens' s $
-              sep commaDel p_larg args
-  ExplicitSum NoExtField tag arity e ->
-    p_unboxedSum N tag arity (located e p_hsExpr)
-  HsCase NoExtField e mgroup ->
-    p_case exprPlacement p_hsExpr e mgroup
-  HsIf NoExtField if' then' else' ->
-    p_if exprPlacement p_hsExpr if' then' else'
-  HsMultiIf NoExtField guards -> do
-    txt "if"
-    breakpoint
-    inci . inci $ sep newline (located' (p_grhs RightArrow)) guards
-  HsLet NoExtField localBinds e ->
-    p_let p_hsExpr localBinds e
-  HsDo NoExtField ctx es -> do
-    let doBody moduleName header = do
-          forM_ moduleName $ \m -> atom m *> txt "."
-          txt header
-          breakpoint
-          ub <- layoutToBraces <$> getLayout
-          inci $
-            sepSemi
-              (ub . withSpacing (p_stmt' exprPlacement (p_hsExpr' S)))
-              (unLoc es)
-        compBody = brackets N . located es $ \xs -> do
-          let p_parBody =
-                sep
-                  (breakpoint >> txt "|" >> space)
-                  p_seqBody
-              p_seqBody =
-                sitcc
-                  . sep
-                    commaDel
-                    (located' (sitcc . p_stmt))
-              stmts = init xs
-              yield = last xs
-              lists = foldr (liftAppend . gatherStmt) [] stmts
-          located yield p_stmt
-          breakpoint
-          txt "|"
-          space
-          p_parBody lists
-    case ctx of
-      DoExpr moduleName -> doBody moduleName "do"
-      MDoExpr moduleName -> doBody moduleName "mdo"
-      ListComp -> compBody
-      MonadComp -> compBody
-      ArrowExpr -> notImplemented "ArrowExpr"
-      GhciStmtCtxt -> notImplemented "GhciStmtCtxt"
-      PatGuard _ -> notImplemented "PatGuard"
-      ParStmtCtxt _ -> notImplemented "ParStmtCtxt"
-      TransStmtCtxt _ -> notImplemented "TransStmtCtxt"
-  ExplicitList _ _ xs ->
-    brackets s $
-      sep commaDel (sitcc . located' p_hsExpr) xs
-  RecordCon {..} -> do
-    located rcon_con_name atom
-    breakpoint
-    let HsRecFields {..} = rcon_flds
-        updName f =
-          (f :: HsRecField GhcPs (LHsExpr GhcPs))
-            { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of
-                FieldOcc _ n -> n
-            }
-        fields = located' (p_hsRecField . updName) <$> rec_flds
-        dotdot =
-          case rec_dotdot of
-            Just {} -> [txt ".."]
-            Nothing -> []
-    inci . braces N $
-      sep commaDel sitcc (fields <> dotdot)
-  RecordUpd {..} -> do
-    located rupd_expr p_hsExpr
-    useRecordDot' <- useRecordDot
-    let mrs sp = case getLoc sp of
-          RealSrcSpan r _ -> Just r
-          _ -> Nothing
-    let isPluginForm =
-          ((1 +) . srcSpanEndCol <$> mrs rupd_expr)
-            == (srcSpanStartCol <$> mrs (head rupd_flds))
-            && onTheSameLine (getLoc rupd_expr) (getLoc $ head rupd_flds)
-    unless (useRecordDot' && isPluginForm) breakpoint
-    let updName f =
-          (f :: HsRecUpdField GhcPs)
-            { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of
-                Ambiguous _ n -> n
-                Unambiguous _ n -> n
-            }
-        updBraces =
-          if useRecordDot' && isPluginForm
-            then recordDotBraces
-            else inci . braces N
-    updBraces $
-      sep
-        commaDel
-        (sitcc . located' (p_hsRecField . updName))
-        rupd_flds
-  ExprWithTySig NoExtField x HsWC {hswc_body = HsIB {..}} -> sitcc $ do
-    located x p_hsExpr
-    space
-    txt "::"
-    breakpoint
-    inci $ located hsib_body p_hsType
-  ArithSeq NoExtField _ x ->
-    case x of
-      From from -> brackets s $ do
-        located from p_hsExpr
-        breakpoint
-        txt ".."
-      FromThen from next -> brackets s $ do
-        sep commaDel (located' p_hsExpr) [from, next]
-        breakpoint
-        txt ".."
-      FromTo from to -> brackets s $ do
-        located from p_hsExpr
-        breakpoint
-        txt ".."
-        space
-        located to p_hsExpr
-      FromThenTo from next to -> brackets s $ do
-        sep commaDel (located' p_hsExpr) [from, next]
-        breakpoint
-        txt ".."
-        space
-        located to p_hsExpr
-  HsBracket NoExtField x -> p_hsBracket x
-  HsRnBracketOut {} -> notImplemented "HsRnBracketOut"
-  HsTcBracketOut {} -> notImplemented "HsTcBracketOut"
-  HsSpliceE NoExtField splice -> p_hsSplice splice
-  HsProc NoExtField p e -> do
-    txt "proc"
-    located p $ \x -> do
-      breakpoint
-      inci (p_pat x)
-      breakpoint
-    txt "->"
-    placeHanging (cmdTopPlacement (unLoc e)) $
-      located e p_hsCmdTop
-  HsStatic _ e -> do
-    txt "static"
-    breakpoint
-    inci (located e p_hsExpr)
-  HsTick {} -> notImplemented "HsTick"
-  HsBinTick {} -> notImplemented "HsBinTick"
-  HsPragE NoExtField prag x -> case prag of
-    HsPragSCC NoExtField _ name -> do
-      txt "{-# SCC "
-      atom name
-      txt " #-}"
-      breakpoint
-      located x p_hsExpr
-    HsPragTick {} -> notImplemented "HsTickPragma"
-
-p_patSynBind :: PatSynBind GhcPs GhcPs -> R ()
-p_patSynBind PSB {..} = do
-  let rhs = do
-        space
-        case psb_dir of
-          Unidirectional -> do
-            txt "<-"
-            breakpoint
-            located psb_def p_pat
-          ImplicitBidirectional -> do
-            equals
-            breakpoint
-            located psb_def p_pat
-          ExplicitBidirectional mgroup -> do
-            txt "<-"
-            breakpoint
-            located psb_def p_pat
-            breakpoint
-            txt "where"
-            breakpoint
-            inci (p_matchGroup (Function psb_id) mgroup)
-  txt "pattern"
-  case psb_args of
-    PrefixCon xs -> do
-      space
-      p_rdrName psb_id
-      inci $ do
-        switchLayout (getLoc <$> xs) $ do
-          unless (null xs) breakpoint
-          sitcc (sep breakpoint p_rdrName xs)
-        rhs
-    RecCon xs -> do
-      space
-      p_rdrName psb_id
-      inci $ do
-        switchLayout (getLoc . recordPatSynPatVar <$> xs) $ do
-          unless (null xs) breakpoint
-          braces N $
-            sep commaDel (p_rdrName . recordPatSynPatVar) xs
-        rhs
-    InfixCon l r -> do
-      switchLayout [getLoc l, getLoc r] $ do
-        space
-        p_rdrName l
-        breakpoint
-        inci $ do
-          p_rdrName psb_id
-          space
-          p_rdrName r
-      inci rhs
-
-p_case ::
-  Data body =>
-  -- | Placer
-  (body -> Placement) ->
-  -- | Render
-  (body -> R ()) ->
-  -- | Expression
-  LHsExpr GhcPs ->
-  -- | Match group
-  MatchGroup GhcPs (Located body) ->
-  R ()
-p_case placer render e mgroup = do
-  txt "case"
-  space
-  located e p_hsExpr
-  space
-  txt "of"
-  breakpoint
-  inci (p_matchGroup' placer render Case mgroup)
-
-p_lamcase ::
-  Data body =>
-  -- | Placer
-  (body -> Placement) ->
-  -- | Render
-  (body -> R ()) ->
-  -- | Expression
-  MatchGroup GhcPs (Located body) ->
-  R ()
-p_lamcase placer render mgroup = do
-  txt "\\case"
-  breakpoint
-  inci (p_matchGroup' placer render LambdaCase mgroup)
-
-p_if ::
-  Data body =>
-  -- | Placer
-  (body -> Placement) ->
-  -- | Render
-  (body -> R ()) ->
-  -- | If
-  LHsExpr GhcPs ->
-  -- | Then
-  Located body ->
-  -- | Else
-  Located body ->
-  R ()
-p_if placer render if' then' else' = do
-  txt "if"
-  space
-  located if' p_hsExpr
-  breakpoint
-  inci $ do
-    txt "then"
-    space
-    located then' $ \x ->
-      placeHanging (placer x) (render x)
-    breakpoint
-    txt "else"
-    space
-    located else' $ \x ->
-      placeHanging (placer x) (render x)
-
-p_let ::
-  Data body =>
-  -- | Render
-  (body -> R ()) ->
-  Located (HsLocalBindsLR GhcPs GhcPs) ->
-  Located body ->
-  R ()
-p_let render localBinds e = sitcc $ do
-  txt "let"
-  space
-  dontUseBraces $ sitcc (located localBinds p_hsLocalBinds)
-  vlayout space (newline >> txt " ")
-  txt "in"
-  space
-  sitcc (located e render)
-
-p_pat :: Pat GhcPs -> R ()
-p_pat = \case
-  WildPat NoExtField -> txt "_"
-  VarPat NoExtField name -> p_rdrName name
-  LazyPat NoExtField pat -> do
-    txt "~"
-    located pat p_pat
-  AsPat NoExtField name pat -> do
-    p_rdrName name
-    txt "@"
-    located pat p_pat
-  ParPat NoExtField pat ->
-    located pat (parens S . p_pat)
-  BangPat NoExtField pat -> do
-    txt "!"
-    located pat p_pat
-  ListPat NoExtField pats ->
-    brackets S $ sep commaDel (located' p_pat) pats
-  TuplePat NoExtField pats boxing -> do
-    let parens' =
-          case boxing of
-            Boxed -> parens S
-            Unboxed -> parensHash S
-    parens' $ sep commaDel (sitcc . located' p_pat) pats
-  SumPat NoExtField pat tag arity ->
-    p_unboxedSum S tag arity (located pat p_pat)
-  ConPat NoExtField pat details ->
-    case details of
-      PrefixCon xs -> sitcc $ do
-        p_rdrName pat
-        unless (null xs) $ do
-          breakpoint
-          inci . sitcc $ sep breakpoint (sitcc . located' p_pat) xs
-      RecCon (HsRecFields fields dotdot) -> do
-        p_rdrName pat
-        breakpoint
-        let f = \case
-              Nothing -> txt ".."
-              Just x -> located x p_pat_hsRecField
-        inci . braces N . sep commaDel f $
-          case dotdot of
-            Nothing -> Just <$> fields
-            Just (L _ n) -> (Just <$> take n fields) ++ [Nothing]
-      InfixCon l r -> do
-        switchLayout [getLoc l, getLoc r] $ do
-          located l p_pat
-          breakpoint
-          inci $ do
-            p_rdrName pat
-            space
-            located r p_pat
-  ViewPat NoExtField expr pat -> sitcc $ do
-    located expr p_hsExpr
-    space
-    txt "->"
-    breakpoint
-    inci (located pat p_pat)
-  SplicePat NoExtField splice -> p_hsSplice splice
-  LitPat NoExtField p -> atom p
-  NPat NoExtField v (isJust -> isNegated) NoExtField -> do
-    when isNegated $ do
-      txt "-"
-      negativeLiterals <- isExtensionEnabled NegativeLiterals
-      when negativeLiterals space
-    located v (atom . ol_val)
-  NPlusKPat NoExtField n k _ _ _ -> sitcc $ do
-    p_rdrName n
-    breakpoint
-    inci $ do
-      txt "+"
-      space
-      located k (atom . ol_val)
-  SigPat NoExtField pat HsPS {..} -> do
-    located pat p_pat
-    p_typeAscription (HsWC NoExtField (HsIB NoExtField hsps_body))
-
-p_pat_hsRecField :: HsRecField' (FieldOcc GhcPs) (LPat GhcPs) -> R ()
-p_pat_hsRecField HsRecField {..} = do
-  located hsRecFieldLbl $ \x ->
-    p_rdrName (rdrNameFieldOcc x)
-  unless hsRecPun $ do
-    space
-    equals
-    breakpoint
-    inci (located hsRecFieldArg p_pat)
-
-p_unboxedSum :: BracketStyle -> ConTag -> Arity -> R () -> R ()
-p_unboxedSum s tag arity m = do
-  let before = tag - 1
-      after = arity - before - 1
-      args = replicate before Nothing <> [Just m] <> replicate after Nothing
-      f x =
-        case x :: Maybe (R ()) of
-          Nothing ->
-            space
-          Just m' -> do
-            space
-            m'
-            space
-  parensHash s $ sep (txt "|") f args
-
-p_hsSplice :: HsSplice GhcPs -> R ()
-p_hsSplice = \case
-  HsTypedSplice NoExtField deco _ expr -> p_hsSpliceTH True expr deco
-  HsUntypedSplice NoExtField deco _ expr -> p_hsSpliceTH False expr deco
-  HsQuasiQuote NoExtField _ quoterName srcSpan str -> do
-    txt "["
-    p_rdrName (L srcSpan quoterName)
-    txt "|"
-    -- QuasiQuoters often rely on precise custom strings. We cannot do any
-    -- formatting here without potentially breaking someone's code.
-    atom str
-    txt "|]"
-  HsSpliced {} -> notImplemented "HsSpliced"
-
-p_hsSpliceTH ::
-  -- | Typed splice?
-  Bool ->
-  -- | Splice expression
-  LHsExpr GhcPs ->
-  -- | Splice decoration
-  SpliceDecoration ->
-  R ()
-p_hsSpliceTH isTyped expr = \case
-  DollarSplice -> do
-    txt decoSymbol
-    located expr (sitcc . p_hsExpr)
-  BareSplice ->
-    located expr (sitcc . p_hsExpr)
-  where
-    decoSymbol = if isTyped then "$$" else "$"
-
-p_hsBracket :: HsBracket GhcPs -> R ()
-p_hsBracket = \case
-  ExpBr NoExtField expr -> do
-    anns <- getEnclosingAnns
-    let name = case anns of
-          AnnOpenEQ : _ -> ""
-          _ -> "e"
-    quote name (located expr p_hsExpr)
-  PatBr NoExtField pat -> located pat (quote "p" . p_pat)
-  DecBrL NoExtField decls -> quote "d" (handleStarIsType decls (p_hsDecls Free decls))
-  DecBrG NoExtField _ -> notImplemented "DecBrG" -- result of renamer
-  TypBr NoExtField ty -> quote "t" (located ty (handleStarIsType ty . p_hsType))
-  VarBr NoExtField isSingleQuote name -> do
-    txt (bool "''" "'" isSingleQuote)
-    -- HACK As you can see we use 'noLoc' here to be able to pass name into
-    -- 'p_rdrName' since the latter expects a "located" thing. The problem
-    -- is that 'VarBr' doesn't provide us with location of the name. This in
-    -- turn makes it impossible to detect if there are parentheses around
-    -- it, etc. So we have to add parentheses manually assuming they are
-    -- necessary for all operators.
-    let isOperator =
-          all
-            (\i -> isPunctuation i || isSymbol i)
-            (showOutputable (rdrNameOcc name))
-            && not (doesNotNeedExtraParens name)
-        wrapper = if isOperator then parens N else id
-    wrapper $ p_rdrName (noLoc name)
-  TExpBr NoExtField expr -> do
-    txt "[||"
-    breakpoint'
-    located expr p_hsExpr
-    breakpoint'
-    txt "||]"
-  where
-    quote :: Text -> R () -> R ()
-    quote name body = do
-      txt "["
-      txt name
-      txt "|"
-      breakpoint'
-      inci $ do
-        dontUseBraces body
-        breakpoint'
-        txt "|]"
-    -- 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
-    -- of an (*|) operator.
-    -- The detection is a bit overcautious, as it adds the spaces as soon as
-    -- HsStarTy is anywhere in the type/declaration.
-    handleStarIsType :: Data a => a -> R () -> R ()
-    handleStarIsType a p
-      | containsHsStarTy a = space *> p <* space
-      | otherwise = p
-      where
-        containsHsStarTy = everything (||) $ \b -> case cast @_ @(HsType GhcPs) b of
-          Just HsStarTy {} -> True
-          _ -> False
-
--- Print the source text of a string literal while indenting
--- gaps correctly.
-
-p_stringLit :: String -> R ()
-p_stringLit src =
-  let s = splitGaps src
-      singleLine =
-        txt $ Text.pack (mconcat s)
-      multiLine =
-        sitcc $ sep breakpoint (txt . Text.pack) (backslashes s)
-   in vlayout singleLine multiLine
-  where
-    -- Split a string on gaps (backslash delimited whitespaces)
-    --
-    -- > splitGaps "bar\\  \\fo\\&o" == ["bar", "fo\\&o"]
-    splitGaps :: String -> [String]
-    splitGaps "" = []
-    splitGaps s =
-      let -- A backslash and a whitespace starts a "gap"
-          p (Just '\\', _, _) = True
-          p (_, '\\', Just c) | ghcSpace c = False
-          p _ = True
-       in case span p (zipPrevNext s) of
-            (l, r) ->
-              let -- drop the initial '\', any amount of 'ghcSpace', and another '\'
-                  r' = drop 1 . dropWhile ghcSpace . drop 1 $ map orig r
-               in map orig l : splitGaps r'
-    -- GHC's definition of whitespaces in strings
-    -- See: https://gitlab.haskell.org/ghc/ghc/blob/86753475/compiler/parser/Lexer.x#L1653
-    ghcSpace :: Char -> Bool
-    ghcSpace c = c <= '\x7f' && is_space c
-    -- Add backslashes to the inner side of the strings
-    --
-    -- > backslashes ["a", "b", "c"] == ["a\\", "\\b\\", "\\c"]
-    backslashes :: [String] -> [String]
-    backslashes (x : y : xs) = (x ++ "\\") : backslashes (('\\' : y) : xs)
-    backslashes xs = xs
-    -- Attaches previous and next items to each list element
-    zipPrevNext :: [a] -> [(Maybe a, a, Maybe a)]
-    zipPrevNext xs =
-      let z =
-            zip
-              (zip (Nothing : map Just xs) xs)
-              (map Just (tail xs) ++ repeat Nothing)
-       in map (\((p, x), n) -> (p, x, n)) z
-    orig (_, x, _) = x
-
-----------------------------------------------------------------------------
--- Helpers
-
--- | Return the wrapping function controlling the use of braces according to
--- the current layout.
-layoutToBraces :: Layout -> R () -> R ()
-layoutToBraces = \case
-  SingleLine -> useBraces
-  MultiLine -> id
-
--- | Append each element in both lists with semigroups. If one list is shorter
--- than the other, return the rest of the longer list unchanged.
-liftAppend :: Semigroup a => [a] -> [a] -> [a]
-liftAppend [] [] = []
-liftAppend [] (y : ys) = y : ys
-liftAppend (x : xs) [] = x : xs
-liftAppend (x : xs) (y : ys) = x <> y : liftAppend xs ys
-
-getGRHSSpan :: GRHS GhcPs (Located body) -> SrcSpan
-getGRHSSpan (GRHS NoExtField guards body) =
-  combineSrcSpans' $ getLoc body :| map getLoc guards
-
--- | Place a thing that may have a hanging form. This function handles how
--- to separate it from preceding expressions and whether to bump indentation
--- depending on what sort of expression we have.
-placeHanging :: Placement -> R () -> R ()
-placeHanging placement m =
-  case placement of
-    Hanging -> do
-      space
-      m
-    Normal -> do
-      breakpoint
-      inci m
-
--- | Check if given block contains single expression which has a hanging
--- form.
-blockPlacement ::
-  (body -> Placement) ->
-  [LGRHS GhcPs (Located body)] ->
-  Placement
-blockPlacement placer [L _ (GRHS NoExtField _ (L _ x))] = placer x
-blockPlacement _ _ = Normal
-
--- | Check if given command has a hanging form.
-cmdPlacement :: HsCmd GhcPs -> Placement
-cmdPlacement = \case
-  HsCmdLam NoExtField _ -> Hanging
-  HsCmdCase NoExtField _ _ -> Hanging
-  HsCmdLamCase NoExtField _ -> Hanging
-  HsCmdDo NoExtField _ -> Hanging
-  _ -> Normal
-
-cmdTopPlacement :: HsCmdTop GhcPs -> Placement
-cmdTopPlacement (HsCmdTop NoExtField (L _ x)) = cmdPlacement x
-
--- | Check if given expression has a hanging form.
-exprPlacement :: HsExpr GhcPs -> Placement
-exprPlacement = \case
-  -- Only hang lambdas with single line parameter lists
-  HsLam NoExtField mg -> case mg of
-    MG _ (L _ [L _ (Match NoExtField _ (x : xs) _)]) _
-      | isOneLineSpan (combineSrcSpans' $ fmap getLoc (x :| xs)) ->
-        Hanging
-    _ -> Normal
-  HsLamCase NoExtField _ -> Hanging
-  HsCase NoExtField _ _ -> Hanging
-  HsDo NoExtField (DoExpr _) _ -> Hanging
-  HsDo NoExtField (MDoExpr _) _ -> Hanging
-  OpApp NoExtField _ op y ->
-    case (fmap getOpNameStr . getOpName . unLoc) op of
-      Just "$" -> exprPlacement (unLoc y)
-      _ -> Normal
-  HsApp NoExtField _ y -> exprPlacement (unLoc y)
-  HsProc NoExtField p _ ->
-    -- Indentation breaks if pattern is longer than one line and left
-    -- hanging. Consequently, only apply hanging when it is safe.
-    if isOneLineSpan (getLoc p)
-      then Hanging
-      else Normal
-  _ -> Normal
-
-withGuards :: [LGRHS GhcPs (Located body)] -> Bool
-withGuards = any (checkOne . unLoc)
-  where
-    checkOne :: GRHS GhcPs (Located body) -> Bool
-    checkOne (GRHS NoExtField [] _) = False
-    checkOne _ = True
-
-exprOpTree :: LHsExpr GhcPs -> OpTree (LHsExpr GhcPs) (LHsExpr GhcPs)
-exprOpTree (L _ (OpApp NoExtField x op y)) = OpBranch (exprOpTree x) op (exprOpTree y)
-exprOpTree n = OpNode n
-
-getOpName :: HsExpr GhcPs -> Maybe RdrName
-getOpName = \case
-  HsVar NoExtField (L _ a) -> Just a
-  _ -> Nothing
-
-getOpNameStr :: RdrName -> String
-getOpNameStr = occNameString . rdrNameOcc
-
-p_exprOpTree ::
-  -- | Bracket style to use
-  BracketStyle ->
-  OpTree (LHsExpr GhcPs) (LHsExpr GhcPs) ->
-  R ()
-p_exprOpTree s (OpNode x) = located x (p_hsExpr' s)
-p_exprOpTree s (OpBranch x op y) = do
-  -- If the beginning of the first argument and the second argument are on
-  -- the same line, and the second argument has a hanging form, use hanging
-  -- placement.
-  let placement =
-        if isOneLineSpan
-          (mkSrcSpan (srcSpanStart (opTreeLoc x)) (srcSpanStart (opTreeLoc y)))
-          then case y of
-            OpNode (L _ n) -> exprPlacement n
-            _ -> Normal
-          else Normal
-      -- Distinguish holes used in infix notation.
-      -- eg. '1 _foo 2' and '1 `_foo` 2'
-      opWrapper = case unLoc op of
-        HsUnboundVar NoExtField _ -> backticks
-        _ -> id
-  layout <- getLayout
-  let ub = case layout of
-        SingleLine -> useBraces
-        MultiLine -> case placement of
-          Hanging -> useBraces
-          Normal -> dontUseBraces
-      opNameStr = (fmap getOpNameStr . getOpName . unLoc) op
-      gotDollar = opNameStr == Just "$"
-      gotColon = opNameStr == Just ":"
-      gotRecordDot = isRecordDot (unLoc op) (opTreeLoc y)
-      lhs =
-        switchLayout [opTreeLoc x] $
-          p_exprOpTree s x
-      p_op = located op (opWrapper . p_hsExpr)
-      p_y = switchLayout [opTreeLoc y] (p_exprOpTree N y)
-      isSection = case (opTreeLoc x, getLoc op) of
-        (RealSrcSpan treeSpan _, RealSrcSpan opSpan _) ->
-          srcSpanEndCol treeSpan /= srcSpanStartCol opSpan
-        _ -> False
-      isDoBlock = \case
-        OpNode (L _ HsDo {}) -> True
-        _ -> False
-  useRecordDot' <- useRecordDot
-  if
-      | gotColon -> do
-        lhs
-        space
-        p_op
-        case placement of
-          Hanging -> do
-            space
-            p_y
-          Normal -> do
-            breakpoint
-            inciIf (isDoBlock y) p_y
-      | gotDollar
-          && isOneLineSpan (opTreeLoc x)
-          && placement == Normal -> do
-        useBraces lhs
-        space
-        p_op
-        breakpoint
-        inci p_y
-      | useRecordDot' && gotRecordDot -> do
-        lhs
-        when isSection space
-        p_op
-        p_y
-      | otherwise -> do
-        ub lhs
-        placeHanging placement $ do
-          p_op
-          space
-          p_y
-
--- | Return 'True' if given expression is a record-dot operator expression.
-isRecordDot ::
-  -- | Operator expression
-  HsExpr GhcPs ->
-  -- | Span of the expression on the right-hand side of the operator
-  SrcSpan ->
-  Bool
-isRecordDot op (RealSrcSpan ySpan _) = case op of
-  HsVar NoExtField (L (RealSrcSpan opSpan _) opName) ->
-    (getOpNameStr opName == ".") && (srcSpanEndCol opSpan == srcSpanStartCol ySpan)
-  _ -> False
-isRecordDot _ _ = False
-
--- | Get annotations for the enclosing element.
-getEnclosingAnns :: R [AnnKeywordId]
-getEnclosingAnns = do
-  e <- getEnclosingSpan (const True)
-  case e of
-    Nothing -> return []
-    Just e' -> getAnns (RealSrcSpan e' Nothing)
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Ormolu.Printer.Meat.Declaration.Value
+  ( p_valDecl,
+    p_pat,
+    p_hsExpr,
+    p_hsUntypedSplice,
+    IsApplicand (..),
+    p_hsExpr',
+    p_hsCmdTop,
+    exprPlacement,
+    cmdTopPlacement,
+  )
+where
+
+import Control.Monad
+import Data.Bool (bool)
+import Data.Data hiding (Infix, Prefix)
+import Data.Function (on)
+import Data.Functor ((<&>))
+import Data.Generics.Schemes (everything)
+import Data.List (intersperse, sortBy, unsnoc)
+import Data.List.NonEmpty (NonEmpty (..), (<|))
+import Data.List.NonEmpty qualified as NE
+import Data.Maybe
+import Data.Text (Text)
+import GHC.Data.Strict qualified as Strict
+import GHC.Hs
+import GHC.LanguageExtensions.Type (Extension (NegativeLiterals))
+import GHC.Types.Basic
+import GHC.Types.Fixity
+import GHC.Types.Name.Reader
+import GHC.Types.SourceText
+import GHC.Types.SrcLoc
+import Language.Haskell.Syntax.Basic
+import Ormolu.Printer.Combinators
+import Ormolu.Printer.Meat.Common
+import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration
+import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree
+import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Signature
+import Ormolu.Printer.Meat.Declaration.StringLiteral
+import Ormolu.Printer.Meat.Type
+import Ormolu.Printer.Operators
+import Ormolu.Utils
+
+-- | Style of a group of equations.
+data MatchGroupStyle
+  = Function (LocatedN RdrName)
+  | PatternBind
+  | Case
+  | Lambda
+  | LambdaCase
+
+-- | Style of equations in a group.
+data GroupStyle
+  = EqualSign
+  | RightArrow
+
+p_valDecl :: HsBind GhcPs -> R ()
+p_valDecl = \case
+  FunBind _ funId funMatches -> p_funBind funId funMatches
+  PatBind _ pat multAnn grhss ->
+    p_match PatternBind False multAnn NoSrcStrict [pat] grhss
+  VarBind {} -> notImplemented "VarBinds" -- introduced by the type checker
+  PatSynBind _ psb -> p_patSynBind psb
+
+p_funBind ::
+  LocatedN RdrName ->
+  MatchGroup GhcPs (LHsExpr GhcPs) ->
+  R ()
+p_funBind name = p_matchGroup (Function name)
+
+p_matchGroup ::
+  MatchGroupStyle ->
+  MatchGroup GhcPs (LHsExpr GhcPs) ->
+  R ()
+p_matchGroup = p_matchGroup' exprPlacement p_hsExpr
+
+p_matchGroup' ::
+  ( Anno (GRHS GhcPs (LocatedA body)) ~ EpAnnCO,
+    Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA
+  ) =>
+  -- | How to get body placement
+  (body -> Placement) ->
+  -- | How to print body
+  (body -> R ()) ->
+  -- | Style of this group of equations
+  MatchGroupStyle ->
+  -- | Match group
+  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 -> bracesIfNecessary
+        LambdaCase -> bracesIfNecessary
+        _ -> dontUseBraces
+        where
+          bracesIfNecessary = if isEmptyMatchGroup mg then useBraces else ub
+  ob $ sepSemi (located' (ub . p_Match)) (unLoc mg_alts)
+  where
+    p_Match m@Match {..} =
+      p_match'
+        placer
+        render
+        (adjustMatchGroupStyle m style)
+        (isInfixMatch m)
+        (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 parentheses) 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.
+adjustMatchGroupStyle ::
+  Match GhcPs body ->
+  MatchGroupStyle ->
+  MatchGroupStyle
+adjustMatchGroupStyle m = \case
+  Function _ | FunRhs {mc_fun = f} <- m_ctxt m -> Function f
+  style -> style
+
+matchStrictness :: Match id body -> SrcStrictness
+matchStrictness = \case
+  Match {m_ctxt = FunRhs {mc_strictness = s}} -> s
+  _ -> NoSrcStrict
+
+p_match ::
+  -- | Style of the group
+  MatchGroupStyle ->
+  -- | Is this an infix match?
+  Bool ->
+  -- | Multiplicity annotation
+  HsMultAnn GhcPs ->
+  -- | Strictness prefix (FunBind)
+  SrcStrictness ->
+  -- | Argument patterns
+  [LPat GhcPs] ->
+  -- | Equations
+  GRHSs GhcPs (LHsExpr GhcPs) ->
+  R ()
+p_match = p_match' exprPlacement p_hsExpr
+
+p_match' ::
+  (Anno (GRHS GhcPs (LocatedA body)) ~ EpAnnCO) =>
+  -- | How to get body placement
+  (body -> Placement) ->
+  -- | How to print body
+  (body -> R ()) ->
+  -- | Style of this group of equations
+  MatchGroupStyle ->
+  -- | Is this an infix match?
+  Bool ->
+  -- | Multiplicity annotation
+  HsMultAnn GhcPs ->
+  -- | Strictness prefix (FunBind)
+  SrcStrictness ->
+  -- | Argument patterns
+  [LPat GhcPs] ->
+  -- | Equations
+  GRHSs GhcPs (LocatedA body) ->
+  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
+  -- 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.
+  p_hsMultAnn (located' p_hsType) multAnn
+  case multAnn of
+    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
+        Function name -> p_rdrName name
+        _ -> return ()
+    Just ne_pats@(head_pat :| tail_pats) -> do
+      let combinedSpans = case style of
+            Function name -> combineSrcSpans (getLocA name) patSpans
+            _ -> patSpans
+          patSpans = combineSrcSpans' (getLocA <$> ne_pats)
+          containsOrPat = everything (||) $ \b -> case cast @_ @(Pat GhcPs) b of
+            Just OrPat {} -> True
+            _ -> False
+          indentBody =
+            not (isOneLineSpan combinedSpans)
+              && not (isCase style && containsOrPat ne_pats)
+      switchLayout [combinedSpans] $ do
+        let stdCase = sep breakpoint (located' p_pat) m_pats
+        case style of
+          Function name ->
+            p_infixDefHelper
+              isInfix
+              indentBody
+              (p_rdrName name)
+              (located' p_pat <$> m_pats)
+          PatternBind -> stdCase
+          Case -> stdCase
+          Lambda -> do
+            let needsSpace = case unLoc head_pat of
+                  LazyPat _ _ -> True
+                  BangPat _ _ -> True
+                  SplicePat _ _ -> True
+                  InvisPat _ _ -> True
+                  _ -> False
+            txt "\\"
+            when needsSpace space
+            sitcc stdCase
+          LambdaCase -> do
+            located' p_pat head_pat
+            unless (null tail_pats) $ do
+              breakpoint
+              -- When we have multiple patterns (with `\cases`) across multiple
+              -- 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.
+      endOfPats = case NE.nonEmpty m_pats of
+        Nothing -> case style of
+          Function name -> Just (getLocA name)
+          _ -> Nothing
+        Just pats -> (Just . getLocA . NE.last) pats
+      hasGuards = withGuards grhssGRHSs
+      grhssSpan =
+        combineSrcSpans' $
+          getGRHSSpan . unLoc <$> grhssGRHSs
+      patGrhssSpan =
+        maybe
+          grhssSpan
+          (combineSrcSpans grhssSpan . srcLocSpan . srcSpanEnd)
+          endOfPats
+      placement =
+        case endOfPats of
+          Just spn
+            | any guardNeedsLineBreak grhssGRHSs
+                || not (onTheSameLine spn grhssSpan) ->
+                Normal
+          _ -> blockPlacement placer grhssGRHSs
+      guardNeedsLineBreak :: XRec GhcPs (GRHS GhcPs body) -> Bool
+      guardNeedsLineBreak (L _ (GRHS _ guardLStmts _)) = case guardLStmts of
+        [] -> False
+        [g] -> not . isOneLineSpan . getLocA $ g
+        _ -> True
+      p_body = do
+        let groupStyle =
+              if isCase style && hasGuards
+                then RightArrow
+                else EqualSign
+        sep
+          breakpoint
+          (located' (p_grhs' placement placer render groupStyle))
+          (NE.toList grhssGRHSs)
+      p_where = do
+        unless (eqEmptyLocalBinds grhssLocalBinds) $ do
+          breakpoint
+          txt "where"
+          breakpoint
+          inci $ p_hsLocalBinds grhssLocalBinds
+  inciIf indentBody $ do
+    unless (length grhssGRHSs > 1) $
+      case style of
+        Function _ | hasGuards -> return ()
+        Function _ -> space >> inci equals
+        PatternBind | hasGuards -> return ()
+        PatternBind -> space >> inci equals
+        s | isCase s && hasGuards -> return ()
+        _ -> space >> txt "->"
+    switchLayout [patGrhssSpan] $
+      placeHanging placement p_body
+    inci p_where
+
+p_grhs :: GroupStyle -> GRHS GhcPs (LHsExpr GhcPs) -> R ()
+p_grhs = p_grhs' Normal exprPlacement p_hsExpr
+
+p_grhs' ::
+  -- | Placement of the parent RHS construct
+  Placement ->
+  -- | How to get body placement
+  (body -> Placement) ->
+  -- | How to print body
+  (body -> R ()) ->
+  GroupStyle ->
+  GRHS GhcPs (LocatedA body) ->
+  R ()
+p_grhs' parentPlacement placer render style (GRHS _ guards body) =
+  case guards of
+    [] -> p_body
+    xs -> do
+      txt "|"
+      space
+      sitcc (sep commaDel (sitcc . located' p_stmt) xs)
+      space
+      inci $ case style of
+        EqualSign -> equals
+        RightArrow -> txt "->"
+      -- 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
+      -- guards are on the same line) we do not need to indent, as it would
+      -- look like double indentation without a good reason.
+      inciIf (parentPlacement == Normal) (placeHanging placement p_body)
+  where
+    placement =
+      case endOfGuards of
+        Nothing -> placer (unLoc body)
+        Just spn ->
+          if onTheSameLine spn (getLocA body)
+            then placer (unLoc body)
+            else Normal
+    endOfGuards =
+      case NE.nonEmpty guards of
+        Nothing -> Nothing
+        Just gs -> (Just . getLocA . NE.last) gs
+    p_body = located body render
+
+p_hsCmd :: HsCmd GhcPs -> R ()
+p_hsCmd = p_hsCmd' NotApplicand N
+
+p_hsCmd' :: IsApplicand -> BracketStyle -> HsCmd GhcPs -> R ()
+p_hsCmd' isApp s = \case
+  HsCmdArrApp _ body input arrType rightToLeft -> do
+    let (l, r) = if rightToLeft then (body, input) else (input, body)
+    located l $ p_hsExpr' NotApplicand s
+    breakpoint
+    inci $ do
+      case (arrType, rightToLeft) of
+        (HsFirstOrderApp, True) -> txt "-<"
+        (HsHigherOrderApp, True) -> txt "-<<"
+        (HsFirstOrderApp, False) -> txt ">-"
+        (HsHigherOrderApp, False) -> txt ">>-"
+      placeHanging (exprPlacement (unLoc input)) $
+        located r p_hsExpr
+  HsCmdArrForm _ form Prefix cmds -> banana s $ do
+    located form p_hsExpr
+    unless (null cmds) $ do
+      breakpoint
+      inci (sequence_ (intersperse breakpoint (located' (p_hsCmdTop N) <$> cmds)))
+  HsCmdArrForm _ form Infix [left, right] -> do
+    modFixityMap <- askModuleFixityMap
+    debug <- askDebug
+    let opTree = BinaryOpBranches (cmdOpTree left) form (cmdOpTree right)
+    p_cmdOpTree
+      s
+      (reassociateOpTree debug (getOpName . unLoc) modFixityMap opTree)
+  HsCmdArrForm _ _ Infix _ -> notImplemented "HsCmdArrForm"
+  HsCmdApp _ cmd expr -> do
+    located cmd (p_hsCmd' Applicand s)
+    breakpoint
+    inci $ located expr p_hsExpr
+  HsCmdLam _ variant mgroup -> p_lam isApp s variant cmdPlacement p_hsCmd mgroup
+  HsCmdPar _ c -> parens N (located c p_hsCmd)
+  HsCmdCase _ 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 _ localBinds c ->
+    p_let p_hsCmd localBinds c
+  HsCmdDo _ es -> do
+    txt "do"
+    p_stmts S isApp cmdPlacement (p_hsCmd' NotApplicand) es
+
+-- | Print a top-level command.
+p_hsCmdTop :: BracketStyle -> HsCmdTop GhcPs -> R ()
+p_hsCmdTop s (HsCmdTop _ cmd) = located cmd (p_hsCmd' NotApplicand s)
+
+-- | Render an expression preserving blank lines between such consecutive
+-- expressions found in the original source code.
+withSpacing ::
+  -- | Rendering function
+  (a -> R ()) ->
+  -- | Entity to render
+  LocatedAn ann a ->
+  R ()
+withSpacing f l = located l $ \x -> do
+  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 ()
+      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)
+
+p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R ()
+p_stmt = p_stmt' N exprPlacement (p_hsExpr' NotApplicand)
+
+p_stmt' ::
+  ( Anno [LStmt GhcPs (XRec GhcPs body)] ~ SrcSpanAnnLW,
+    Anno (Stmt GhcPs (XRec GhcPs body)) ~ SrcSpanAnnA,
+    Anno body ~ SrcSpanAnnA
+  ) =>
+  BracketStyle ->
+  -- | Placer
+  (body -> Placement) ->
+  -- | Render
+  (BracketStyle -> body -> R ()) ->
+  -- | Statement to render
+  Stmt GhcPs (XRec GhcPs body) ->
+  R ()
+p_stmt' s placer render = \case
+  LastStmt _ body _ _ -> located body (render s)
+  BindStmt _ p f@(getLocA -> l) -> do
+    located p p_pat
+    space
+    txt "<-"
+    let loc = getLocA p
+        placement
+          | isOneLineSpan (mkSrcSpan (srcSpanEnd loc) (srcSpanStart l)) = placer (unLoc f)
+          | otherwise = Normal
+    switchLayout [loc, l] $
+      placeHanging placement (located f (render N))
+  BodyStmt _ body _ _ -> located body (render s)
+  LetStmt _ binds -> do
+    txt "let"
+    space
+    sitcc $ p_hsLocalBinds binds
+  ParStmt {} ->
+    -- 'ParStmt' should always be eliminated in 'gatherStmts' already, such
+    -- 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
+    -- handled through 'gatherStmts'.
+    case (trS_form, trS_by) of
+      (ThenForm, Nothing) -> do
+        txt "then"
+        breakpoint
+        inci $ located trS_using p_hsExpr
+      (ThenForm, Just e) -> do
+        txt "then"
+        breakpoint
+        inci $ located trS_using p_hsExpr
+        breakpoint
+        txt "by"
+        breakpoint
+        inci $ located e p_hsExpr
+      (GroupForm, Nothing) -> do
+        txt "then group using"
+        breakpoint
+        inci $ located trS_using p_hsExpr
+      (GroupForm, Just e) -> do
+        txt "then group by"
+        breakpoint
+        inci $ located e p_hsExpr
+        breakpoint
+        txt "using"
+        breakpoint
+        inci $ located trS_using p_hsExpr
+  RecStmt {..} -> do
+    txt "rec"
+    space
+    sitcc . located recS_stmts $ sepSemi (withSpacing (p_stmt' s placer render))
+
+p_stmts ::
+  ( Anno [LStmt GhcPs (XRec GhcPs body)] ~ SrcSpanAnnLW,
+    Anno (Stmt GhcPs (XRec GhcPs body)) ~ SrcSpanAnnA,
+    Anno body ~ SrcSpanAnnA
+  ) =>
+  BracketStyle ->
+  IsApplicand ->
+  -- | Placer
+  (body -> Placement) ->
+  -- | Render
+  (BracketStyle -> body -> R ()) ->
+  -- | Statements to render
+  XRec GhcPs [LStmt GhcPs (XRec GhcPs body)] ->
+  R ()
+p_stmts s isApp placer render es = do
+  breakpoint
+  ub <- layoutToBraces <$> getLayout
+  let p_stmtExt (relPos, stmt) =
+        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).
+          ub' = case relPos of
+            FirstPos -> ub
+            MiddlePos -> ub
+            LastPos -> id
+            SinglePos -> id
+  inciApplicand isApp . located es $
+    sepSemi p_stmtExt . attachRelativePos
+
+p_hsLocalBinds :: HsLocalBinds GhcPs -> R ()
+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.
+    br <- layoutToBraces <$> getLayout
+    let items =
+          let injectLeft (L l x) = L l (Left x)
+              injectRight (L l x) = L l (Right x)
+           in (injectLeft <$> binds) ++ (injectRight <$> lsigs)
+        positionToBracing = \case
+          SinglePos -> id
+          FirstPos -> br
+          MiddlePos -> br
+          LastPos -> id
+        p_item' (p, item) =
+          positionToBracing p $
+            withSpacing (either p_valDecl p_sigDecl) item
+        items' = sortBy (leftmost_smallest `on` getLocA) items
+    sitcc $ sepSemi p_item' (attachRelativePos items')
+  HsValBinds _ _ -> notImplemented "HsValBinds"
+  HsIPBinds epAnn (IPBinds _ xs) -> pseudoLocated epAnn $ do
+    let p_ipBind (IPBind _ (L _ name) expr) = do
+          atom @HsIPName name
+          space
+          equals
+          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.
+    pseudoLocated = \case
+      EpAnn {anns = AnnList {al_anchor}}
+        | -- excluding cases where there are no bindings
+          not $ isZeroWidthSpan (locA al_anchor) ->
+            located (L al_anchor ()) . const
+      _ -> id
+
+p_dotFieldOcc :: DotFieldOcc GhcPs -> R ()
+p_dotFieldOcc =
+  p_rdrName . fmap (mkVarUnqual . field_label) . dfoLabel
+
+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
+
+p_hsFieldBind ::
+  (lhs ~ GenLocated l a, HasLoc l) =>
+  (lhs -> R ()) ->
+  HsFieldBind lhs (LHsExpr GhcPs) ->
+  R ()
+p_hsFieldBind p_lhs HsFieldBind {..} = do
+  p_lhs hfbLHS
+  unless hfbPun $ do
+    space
+    equals
+    let placement =
+          if onTheSameLine (getLocA hfbLHS) (getLocA hfbRHS)
+            then exprPlacement (unLoc hfbRHS)
+            else Normal
+    placeHanging placement (located hfbRHS p_hsExpr)
+
+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 indentation in cases like
+--
+-- > foo =
+-- >   do
+-- >       succ
+-- >     1
+data IsApplicand = Applicand | NotApplicand
+
+inciApplicand :: IsApplicand -> R () -> R ()
+inciApplicand = \case
+  Applicand -> inci . inci
+  NotApplicand -> inci
+
+-- | Adjust bracing as needed for certain cases e.g. 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
+  HsOverLabel sourceText _ -> do
+    txt "#"
+    p_sourceText sourceText
+  HsIPVar _ (HsIPName name) -> do
+    txt "?"
+    atom name
+  HsOverLit _ v -> atom (ol_val v)
+  HsLit _ lit ->
+    case lit of
+      HsString (SourceText stxt) _ -> p_stringLit stxt
+      HsStringPrim (SourceText stxt) _ -> p_stringLit stxt
+      HsMultilineString (SourceText stxt) _ -> p_stringLit stxt
+      r -> atom r
+  HsLam _ variant 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
+        -- parameters together.
+        gatherArgs f' knownArgs =
+          case f' of
+            L _ (HsApp _ l r) -> gatherArgs l (r <| knownArgs)
+            _ -> (f', knownArgs)
+        (func, args) = gatherArgs f (x :| [])
+        -- We need to handle the last argument specially if it is a
+        -- hanging construct, so separate it from the rest.
+        (initp, lastp) = (NE.init args, NE.last args)
+        initSpan =
+          combineSrcSpans' $
+            getLocA f :| [(srcLocSpan . srcSpanStart . getLocA) lastp]
+        -- Hang the last argument only if the initial arguments span one
+        -- line.
+        placement =
+          if isOneLineSpan initSpan
+            then exprPlacement (unLoc lastp)
+            else 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.
+    case placement of
+      Normal -> do
+        ub <-
+          getLayout <&> \case
+            SingleLine -> useBraces
+            MultiLine -> id
+        ub $ do
+          located func (p_hsExpr' Applicand s)
+          breakpoint
+          inci $ sep breakpoint (located' p_hsExpr) initp
+        inci $ do
+          unless (null initp) breakpoint
+          located lastp p_hsExpr
+      Hanging -> do
+        useBraces . switchLayout [initSpan] $ do
+          located func (p_hsExpr' Applicand s)
+          breakpoint
+          sep breakpoint (located' p_hsExpr) initp
+        placeHanging placement $
+          located lastp p_hsExpr
+  HsAppType _ e a -> do
+    located e p_hsExpr
+    breakpoint
+    inci $ do
+      txt "@"
+      located (hswc_body a) p_hsType
+  OpApp _ x op y -> do
+    modFixityMap <- askModuleFixityMap
+    debug <- askDebug
+    let opTree = BinaryOpBranches (exprOpTree x) op (exprOpTree y)
+    p_exprOpTree
+      s
+      (reassociateOpTree debug (getOpName . unLoc) modFixityMap opTree)
+  NegApp _ e _ -> do
+    negativeLiterals <- isExtensionEnabled NegativeLiterals
+    let isLiteral = case unLoc e of
+          HsLit {} -> True
+          HsOverLit {} -> True
+          _ -> False
+    txt "-"
+    -- If NegativeLiterals is enabled, we have to insert a space before
+    -- negated literals, as `- 1` and `-1` have differing AST.
+    when (negativeLiterals && isLiteral) space
+    located e p_hsExpr
+  HsPar _ e -> do
+    csSpans <-
+      fmap (flip RealSrcSpan Strict.Nothing . getLoc) <$> getEnclosingComments
+    switchLayout (locA e : csSpans) $
+      parens s (located e (dontUseBraces . p_hsExpr))
+  SectionL _ x op -> do
+    located x p_hsExpr
+    breakpoint
+    inci (located op p_hsExpr)
+  SectionR _ op x -> do
+    located op p_hsExpr
+    breakpoint
+    inci (located x p_hsExpr)
+  ExplicitTuple _ args boxity -> do
+    let isSection = any isMissing args
+        isMissing = \case
+          Missing _ -> True
+          _ -> False
+        p_arg =
+          sitcc . \case
+            Present _ x -> located x p_hsExpr
+            Missing _ -> pure ()
+        parens' =
+          case boxity of
+            Boxed -> parens
+            Unboxed -> parensHash
+    enclSpan <-
+      fmap (flip RealSrcSpan Strict.Nothing) . maybeToList
+        <$> getEnclosingSpan
+    if isSection
+      then
+        switchLayout [] . parens' s $
+          sep comma p_arg args
+      else
+        switchLayout enclSpan . parens' s $
+          sep commaDel p_arg args
+  ExplicitSum _ tag arity e ->
+    p_unboxedSum N tag arity (located e p_hsExpr)
+  HsCase _ 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)) (NE.toList guards)
+  HsLet _ localBinds e ->
+    p_let p_hsExpr localBinds e
+  HsDo _ doFlavor es -> do
+    let doBody moduleName header = do
+          forM_ moduleName $ \m -> atom m *> txt "."
+          txt header
+          p_stmts S isApp exprPlacement (p_hsExpr' NotApplicand) es
+    case doFlavor of
+      DoExpr moduleName -> doBody moduleName "do"
+      MDoExpr moduleName -> doBody moduleName "mdo"
+      ListComp -> p_listComp s es
+      MonadComp -> p_listComp s es
+      GhciStmtCtxt -> notImplemented "GhciStmtCtxt"
+  ExplicitList _ xs ->
+    brackets s $
+      sep commaDel (sitcc . located' p_hsExpr) xs
+  RecordCon {..} -> do
+    p_rdrName rcon_con
+    breakpoint
+    let HsRecFields {..} = rcon_flds
+        p_lhs = located' $ p_rdrName . foLabel
+        fields = located' (p_hsFieldBind p_lhs) <$> rec_flds
+        dotdot = case rec_dotdot of
+          Just {} -> [txt ".."]
+          Nothing -> []
+    inci . braces N $
+      sep commaDel sitcc (fields <> dotdot)
+  RecordUpd {..} -> do
+    located rupd_expr p_hsExpr
+    breakpoint
+    let p_recFields p_lbl =
+          sep commaDel (sitcc . located' (p_hsFieldBind p_lbl))
+        p_fieldLabelStrings (FieldLabelStrings flss) =
+          p_dotFieldOccs $ unLoc <$> flss
+    inci . braces N $ case rupd_flds of
+      RegularRecUpdFields {..} ->
+        p_recFields (located' p_fieldOcc) recUpdFields
+      OverloadedRecUpdFields {..} ->
+        p_recFields (located' p_fieldLabelStrings) olRecUpdFields
+  HsGetField {..} -> do
+    located gf_expr p_hsExpr
+    txt "."
+    located gf_field p_dotFieldOcc
+  HsProjection {..} -> parens N $ do
+    txt "."
+    p_dotFieldOccs proj_flds
+  ExprWithTySig _ x HsWC {hswc_body} -> sitcc $ do
+    located x p_hsExpr
+    space
+    txt "::"
+    breakpoint
+    inci $ located hswc_body p_hsSigType
+  ArithSeq _ _ x ->
+    case x of
+      From from -> brackets s $ do
+        located from p_hsExpr
+        breakpoint
+        txt ".."
+      FromThen from next -> brackets s $ do
+        sep commaDel (located' p_hsExpr) [from, next]
+        breakpoint
+        txt ".."
+      FromTo from to -> brackets s $ do
+        located from p_hsExpr
+        breakpoint
+        txt ".."
+        space
+        located to p_hsExpr
+      FromThenTo from next to -> brackets s $ do
+        sep commaDel (located' p_hsExpr) [from, next]
+        breakpoint
+        txt ".."
+        space
+        located to p_hsExpr
+  HsTypedBracket _ expr -> do
+    txt "[||"
+    breakpoint'
+    located expr p_hsExpr
+    breakpoint'
+    txt "||]"
+  HsUntypedBracket _ x -> p_hsQuote x
+  HsTypedSplice _ (HsTypedSpliceExpr _ expr) -> p_hsSpliceTH True expr DollarSplice
+  HsUntypedSplice _ untySplice -> p_hsUntypedSplice DollarSplice untySplice
+  HsProc _ p e -> do
+    txt "proc"
+    located p $ \x -> do
+      breakpoint
+      inci (p_pat x)
+      breakpoint
+    txt "->"
+    placeHanging (cmdTopPlacement (unLoc e)) $
+      located e (p_hsCmdTop N)
+  HsStatic _ e -> do
+    txt "static"
+    breakpoint
+    inci (located e p_hsExpr)
+  HsPragE _ prag x -> case prag of
+    HsPragSCC _ name -> do
+      txt "{-# SCC "
+      atom name
+      txt " #-}"
+      breakpoint
+      let inciIfS = case s of N -> id; S -> inci
+      inciIfS $ located x p_hsExpr
+  HsEmbTy _ HsWC {hswc_body} -> do
+    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
+  HsForAll _ tele e -> do
+    p_hsForAllTelescope tele
+    breakpoint
+    located e p_hsExpr
+  -- similar to HsQualTy
+  HsQual _ qs e -> do
+    located qs $ p_hsContext' p_hsExpr
+    space
+    txt "=>"
+    breakpoint
+    located e p_hsExpr
+  -- similar to HsFunTy
+  HsFunArr _ multAnn x y -> do
+    located x p_hsExpr
+    space
+    p_hsMultAnn (located' p_hsExpr) multAnn
+    space
+    txt "->"
+    breakpoint
+    case unLoc y of
+      HsFunArr {} -> p_hsExpr (unLoc y)
+      _ -> located y p_hsExpr
+
+-- | Print a list comprehension.
+--
+-- BracketStyle should be N except in a do-block, which 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
+    singleLine = do
+      txt "["
+      body
+      txt "]"
+    multiLine = do
+      txt "[" >> space
+      (if s == S then sitcc else id) $ do
+        body
+        newline
+        txt "]"
+
+    body = located es p_body
+    p_body xs = do
+      let (stmts, yield) =
+            case unsnoc xs of
+              Nothing -> error $ "list comprehension unexpectedly had no expressions"
+              Just (ys, y) -> (ys, y)
+      sitcc $ located yield p_stmt
+      breakpoint
+      txt "|"
+      space
+      p_bodyParallels (gatherStmts stmts)
+
+    -- 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) (sitcc . p_bodyParallelStmts)
+
+    -- 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))
+
+-- | Gather the set of statements in a list comprehension.
+--
+-- For example, this code:
+--
+-- @
+-- [ a + b + c + d
+-- | a <- as, let b = a + a
+-- | c <- cs
+-- | d <- ds, then sort by f
+-- ]
+-- @
+--
+-- is parsed as roughly:
+--
+-- @
+-- [ ParStmt
+--     [ ParStmtBlock
+--         [ BindStmt [| a <- as |]
+--         , LetStmt  [| let b = a + a |]
+--         ]
+--     , ParStmtBlock
+--         [ BindStmt [| c <- cs |]
+--         ]
+--     , ParStmtBlock
+--         [ TransStmt
+--             [ BindStmt [| d <- ds |]
+--             ]
+--             [| then sort by f |]
+--         ]
+--     ]
+-- , LastStmt [| a + b + c + d |]
+-- ]
+-- @
+--
+-- 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
+-- normalizes it into:
+--
+-- @
+-- [ [ BindStmt [| a <- as |]
+--   , LetStmt  [| let b = a + a |]
+--   ]
+-- , [ BindStmt [| c <- cs |]
+--   ]
+-- , [ BindStmt [| d <- ds |]
+--   , TransStmt [] [| then sortWith by f |]
+--   ]
+-- ]
+-- @
+--
+-- Notes:
+--   * The number of elements in the outer list is the number of pipes in
+--     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.
+  [L _ (ParStmt _ blocks _ _)] ->
+    [ concatMap collectNonParStmts stmts
+    | ParStmtBlock _ stmts _ _ <- NE.toList blocks
+    ]
+  -- Otherwise, list will not contain any ParStmt
+  stmts ->
+    [ concatMap collectNonParStmts stmts
+    ]
+  where
+    collectNonParStmts = \case
+      L _ ParStmt {} -> unexpected "ParStmt"
+      stmt@(L _ TransStmt {trS_stmts}) -> concatMap collectNonParStmts trS_stmts ++ [stmt]
+      stmt -> [stmt]
+
+    unexpected label = error $ "Unexpected " <> label <> "! Please file a bug."
+
+p_patSynBind :: PatSynBind GhcPs GhcPs -> R ()
+p_patSynBind PSB {..} = do
+  let rhs conSpans = do
+        space
+        let pattern_def_spans = [getLocA psb_id, getLocA psb_def] ++ conSpans
+        case psb_dir of
+          Unidirectional ->
+            switchLayout pattern_def_spans $ do
+              txt "<-"
+              breakpoint
+              located psb_def p_pat
+          ImplicitBidirectional ->
+            switchLayout pattern_def_spans $ do
+              equals
+              breakpoint
+              located psb_def p_pat
+          ExplicitBidirectional mgroup -> do
+            switchLayout pattern_def_spans $ do
+              txt "<-"
+              breakpoint
+              located psb_def p_pat
+            breakpoint
+            txt "where"
+            breakpoint
+            inci (p_matchGroup (Function psb_id) mgroup)
+  txt "pattern"
+  case psb_args of
+    PrefixCon xs -> do
+      space
+      p_rdrName psb_id
+      inci $ do
+        let conSpans = getLocA <$> xs
+        switchLayout conSpans $ do
+          unless (null xs) breakpoint
+          sitcc (sep breakpoint p_rdrName xs)
+        rhs conSpans
+    RecCon xs -> do
+      space
+      p_rdrName psb_id
+      inci $ do
+        let conSpans = getLocA . recordPatSynPatVar <$> xs
+        switchLayout conSpans $ do
+          unless (null xs) breakpoint
+          braces N $
+            sep commaDel (p_rdrName . recordPatSynPatVar) xs
+        rhs conSpans
+    InfixCon l r -> do
+      let conSpans = [getLocA l, getLocA r]
+      switchLayout conSpans $ do
+        space
+        p_rdrName l
+        breakpoint
+        inci $ do
+          p_rdrName psb_id
+          space
+          p_rdrName r
+      inci (rhs conSpans)
+
+p_case ::
+  ( Anno (GRHS GhcPs (LocatedA body)) ~ EpAnnCO,
+    Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA
+  ) =>
+  IsApplicand ->
+  BracketStyle ->
+  -- | Placer
+  (body -> Placement) ->
+  -- | Render
+  (body -> R ()) ->
+  -- | Expression
+  LHsExpr GhcPs ->
+  -- | Match group
+  MatchGroup GhcPs (LocatedA body) ->
+  R ()
+p_case isApp s placer render e mgroup = do
+  txt "case"
+  space
+  located e p_hsExpr
+  space
+  txt "of"
+  breakpoint
+  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
+  (body -> Placement) ->
+  -- | Render
+  (body -> R ()) ->
+  -- | Expression
+  MatchGroup GhcPs (LocatedA body) ->
+  R ()
+p_lam isApp s variant placer render mgroup = do
+  let mCaseTxt = case variant of
+        LamSingle -> Nothing
+        LamCase -> Just "\\case"
+        LamCases -> Just "\\cases"
+      mgs = if isJust mCaseTxt then LambdaCase else Lambda
+      pMatchGroup = p_matchGroup' placer render mgs mgroup
+  case mCaseTxt of
+    Nothing -> pMatchGroup
+    Just caseTxt -> do
+      txt caseTxt
+      breakpoint
+      adjustBracing isApp s (inciApplicand isApp pMatchGroup)
+
+p_if ::
+  -- | Placer
+  (body -> Placement) ->
+  -- | Render
+  (body -> R ()) ->
+  -- | Annotations
+  AnnsIf ->
+  -- | If
+  LHsExpr GhcPs ->
+  -- | Then
+  LocatedA body ->
+  -- | Else
+  LocatedA body ->
+  R ()
+p_if placer render anns if' then' else' = do
+  txt "if"
+  space
+  located if' p_hsExpr
+  breakpoint
+  commentSpans <- fmap getLoc <$> getEnclosingComments
+  let (thenSpan, elseSpan) = (locA aiThen, locA aiElse)
+        where
+          AnnsIf {aiThen, aiElse} = anns
+
+      locatedToken tokenSpan token =
+        located (L tokenSpan ()) $ \_ -> txt token
+
+      betweenSpans spanA spanB s = spanA < s && s < spanB
+
+      placeHangingLocated tokenSpan bodyLoc@(L _ body) = do
+        let bodySpan = getLocA bodyLoc
+            hasComments = fromMaybe False $ do
+              tokenRealSpan <- srcSpanToRealSrcSpan tokenSpan
+              bodyRealSpan <- srcSpanToRealSrcSpan bodySpan
+              pure $ any (betweenSpans tokenRealSpan bodyRealSpan) commentSpans
+            placement = if hasComments then Normal else placer body
+        switchLayout [tokenSpan, bodySpan] $
+          placeHanging placement (located bodyLoc render)
+  inci $ do
+    locatedToken thenSpan "then"
+    space
+    placeHangingLocated thenSpan then'
+    breakpoint
+    locatedToken elseSpan "else"
+    space
+    placeHangingLocated elseSpan else'
+
+p_let ::
+  -- | Render
+  (body -> R ()) ->
+  HsLocalBinds GhcPs ->
+  LocatedA body ->
+  R ()
+p_let render localBinds e = sitcc $ do
+  txt "let"
+  space
+  dontUseBraces $ sitcc (p_hsLocalBinds localBinds)
+  vlayout space (newline >> txt " ")
+  txt "in"
+  space
+  sitcc (located e render)
+
+p_pat :: Pat GhcPs -> R ()
+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' inAsPat)
+  AsPat _ name pat -> do
+    p_rdrName name
+    txt "@"
+    located pat (p_pat' True)
+  ParPat _ pat ->
+    located pat (parens S . p_pat' inAsPat)
+  BangPat _ pat -> do
+    txt "!"
+    located pat (p_pat' inAsPat)
+  ListPat _ 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' 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' inAsPat))
+  ConPat _ pat details ->
+    case details of
+      PrefixCon xs -> sitcc $ do
+        p_rdrName pat
+        unless (null xs) breakpoint
+        inci . sitcc $ sep breakpoint (sitcc . located' (p_pat' inAsPat)) xs
+      RecCon (HsRecFields _ fields dotdot) -> do
+        p_rdrName pat
+        breakpoint
+        let f = \case
+              Nothing -> txt ".."
+              Just x -> located x p_pat_hsFieldBind
+        inci . braces N . 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' inAsPat)
+          breakpoint
+          inci $ do
+            p_rdrName pat
+            space
+            located r (p_pat' inAsPat)
+  ViewPat _ expr pat -> sitcc $ do
+    located expr p_hsExpr
+    space
+    txt "->"
+    breakpoint
+    inci (located pat (p_pat' inAsPat))
+  SplicePat _ splice -> p_hsUntypedSplice DollarSplice splice
+  LitPat _ p -> atom p
+  NPat _ v (isJust -> isNegated) _ -> do
+    when isNegated $ do
+      txt "-"
+      negativeLiterals <- isExtensionEnabled NegativeLiterals
+      when negativeLiterals space
+    located v (atom . ol_val)
+  NPlusKPat _ n k _ _ _ -> sitcc $ do
+    p_rdrName n
+    breakpoint
+    inci $ do
+      txt "+"
+      space
+      located k (atom . ol_val)
+  SigPat _ pat HsPS {..} -> do
+    located pat (p_pat' inAsPat)
+    p_typeAscription (lhsTypeToSigType hsps_body)
+  EmbTyPat _ (HsTP _ ty) -> do
+    txt "type"
+    space
+    located ty p_hsType
+  InvisPat _ tyPat -> p_tyPat tyPat
+
+p_tyPat :: HsTyPat GhcPs -> R ()
+p_tyPat (HsTP _ ty) = txt "@" *> located ty p_hsType
+
+p_pat_hsFieldBind :: HsRecField GhcPs (LPat GhcPs) -> R ()
+p_pat_hsFieldBind HsFieldBind {..} = do
+  located hfbLHS p_fieldOcc
+  unless hfbPun $ do
+    space
+    equals
+    breakpoint
+    inci (located hfbRHS p_pat)
+
+p_unboxedSum :: BracketStyle -> ConTag -> Arity -> R () -> R ()
+p_unboxedSum s tag arity m = do
+  let before = tag - 1
+      after = arity - before - 1
+      args = replicate before Nothing <> [Just m] <> replicate after Nothing
+      f x =
+        case x :: Maybe (R ()) of
+          Nothing ->
+            space
+          Just m' -> do
+            space
+            m'
+            space
+  parensHash s $ sep (txt "|") f args
+
+p_hsUntypedSplice :: SpliceDecoration -> HsUntypedSplice GhcPs -> R ()
+p_hsUntypedSplice deco = \case
+  HsUntypedSpliceExpr _ expr -> p_hsSpliceTH False expr deco
+  HsQuasiQuote _ quoterName str -> do
+    txt "["
+    p_rdrName quoterName
+    txt "|"
+    -- QuasiQuoters often rely on precise custom strings. We cannot do any
+    -- formatting here without potentially breaking someone's code.
+    atom str
+    txt "|]"
+
+p_hsSpliceTH ::
+  -- | Typed splice?
+  Bool ->
+  -- | Splice expression
+  LHsExpr GhcPs ->
+  -- | Splice decoration
+  SpliceDecoration ->
+  R ()
+p_hsSpliceTH isTyped expr = \case
+  DollarSplice -> do
+    txt decoSymbol
+    located expr (sitcc . p_hsExpr)
+  BareSplice ->
+    located expr (sitcc . p_hsExpr)
+  where
+    decoSymbol = if isTyped then "$$" else "$"
+
+p_hsQuote :: HsQuote GhcPs -> R ()
+p_hsQuote = \case
+  ExpBr (bracketAnn, _) expr -> do
+    let name = case bracketAnn of
+          BracketNoE {} -> ""
+          BracketHasE {} -> "e"
+    quote name (located expr p_hsExpr)
+  PatBr _ pat -> located pat (quote "p" . p_pat)
+  DecBrL _ decls -> quote "d" (handleStarIsType decls (p_hsDecls Free decls))
+  DecBrG _ _ -> notImplemented "DecBrG" -- result of renamer
+  TypBr _ ty -> quote "t" (located ty (handleStarIsType ty . p_hsType))
+  VarBr _ isSingleQuote name -> do
+    txt (bool "''" "'" isSingleQuote)
+    p_rdrName name
+  where
+    quote :: Text -> R () -> R ()
+    quote name body = do
+      txt "["
+      txt name
+      txt "|"
+      breakpoint'
+      inci $ do
+        dontUseBraces body
+        breakpoint'
+        txt "|]"
+    -- 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
+    -- of an (*|) operator.
+    -- The detection is a bit overcautious, as it adds the spaces as soon as
+    -- HsStarTy is anywhere in the type/declaration.
+    handleStarIsType :: (Data a) => a -> R () -> R ()
+    handleStarIsType a p
+      | containsHsStarTy a = space *> p <* space
+      | otherwise = p
+      where
+        containsHsStarTy = everything (||) $ \b -> case cast @_ @(HsType GhcPs) b of
+          Just HsStarTy {} -> True
+          _ -> False
+
+----------------------------------------------------------------------------
+-- Helpers
+
+-- | Return the wrapping function controlling the use of braces according to
+-- the current layout.
+layoutToBraces :: Layout -> R () -> R ()
+layoutToBraces = \case
+  SingleLine -> useBraces
+  MultiLine -> id
+
+getGRHSSpan :: GRHS GhcPs (LocatedA body) -> SrcSpan
+getGRHSSpan (GRHS _ guards body) =
+  combineSrcSpans' $ getLocA body :| map getLocA guards
+
+-- | Determine placement of a given block.
+blockPlacement ::
+  (body -> Placement) ->
+  NonEmpty (LGRHS GhcPs (LocatedA body)) ->
+  Placement
+blockPlacement placer (L _ (GRHS _ _ (L _ x)) :| []) = placer x
+blockPlacement _ _ = Normal
+
+-- | Determine placement of a given command.
+cmdPlacement :: HsCmd GhcPs -> Placement
+cmdPlacement = \case
+  HsCmdLam {} -> Hanging
+  HsCmdCase {} -> Hanging
+  HsCmdDo {} -> Hanging
+  _ -> Normal
+
+-- | Determine placement of a top level command.
+cmdTopPlacement :: HsCmdTop GhcPs -> Placement
+cmdTopPlacement (HsCmdTop _ (L _ x)) = cmdPlacement x
+
+-- | Check if given expression has a hanging form.
+exprPlacement :: HsExpr GhcPs -> Placement
+exprPlacement = \case
+  -- 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)) _)])
+        | isOneLineSpan (combineSrcSpans' $ fmap getLocA (x :| xs)) ->
+            Hanging
+      _ -> Normal
+    LamCase -> Hanging
+    LamCases -> Hanging
+  HsCase _ _ _ -> Hanging
+  HsDo _ (DoExpr _) _ -> Hanging
+  HsDo _ (MDoExpr _) _ -> Hanging
+  OpApp _ _ op y ->
+    case (fmap getOpNameStr . getOpName . unLoc) op of
+      Just "$" -> exprPlacement (unLoc y)
+      _ -> Normal
+  HsApp _ _ y -> exprPlacement (unLoc y)
+  HsProc _ p _ ->
+    -- Indentation breaks if pattern is longer than one line and left
+    -- hanging. Consequently, only apply hanging when it is safe.
+    if isOneLineSpan (getLocA p)
+      then Hanging
+      else Normal
+  _ -> Normal
+
+-- | Return 'True' if any of the RHS expressions has guards.
+withGuards :: NonEmpty (LGRHS GhcPs body) -> Bool
+withGuards = any (checkOne . unLoc)
+  where
+    checkOne (GRHS _ [] _) = False
+    checkOne _ = True
diff --git a/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot b/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot
--- a/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot
+++ b/src/Ormolu/Printer/Meat/Declaration/Value.hs-boot
@@ -2,19 +2,25 @@
   ( p_valDecl,
     p_pat,
     p_hsExpr,
-    p_hsSplice,
-    p_stringLit,
+    p_hsUntypedSplice,
+    p_hsExpr',
+    p_hsCmdTop,
+    exprPlacement,
+    cmdTopPlacement,
   )
 where
 
-import GHC.Hs.Binds
-import GHC.Hs.Expr
-import GHC.Hs.Extension
-import GHC.Hs.Pat
+import GHC.Hs
 import Ormolu.Printer.Combinators
 
 p_valDecl :: HsBindLR GhcPs GhcPs -> R ()
 p_pat :: Pat GhcPs -> R ()
 p_hsExpr :: HsExpr GhcPs -> R ()
-p_hsSplice :: HsSplice GhcPs -> R ()
-p_stringLit :: String -> R ()
+p_hsUntypedSplice :: SpliceDecoration -> HsUntypedSplice GhcPs -> R ()
+
+data IsApplicand
+
+p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R ()
+p_hsCmdTop :: BracketStyle -> HsCmdTop GhcPs -> R ()
+exprPlacement :: HsExpr GhcPs -> Placement
+cmdTopPlacement :: HsCmdTop GhcPs -> Placement
diff --git a/src/Ormolu/Printer/Meat/Declaration/Warning.hs b/src/Ormolu/Printer/Meat/Declaration/Warning.hs
--- a/src/Ormolu/Printer/Meat/Declaration/Warning.hs
+++ b/src/Ormolu/Printer/Meat/Declaration/Warning.hs
@@ -1,50 +1,54 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 
 module Ormolu.Printer.Meat.Declaration.Warning
   ( p_warnDecls,
-    p_moduleWarning,
+    p_warningTxt,
   )
 where
 
 import Data.Foldable
 import Data.Text (Text)
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Types.Basic
-import GHC.Types.Name.Reader
+import Data.Text qualified as T
+import GHC.Hs
+import GHC.Types.SourceText
 import GHC.Types.SrcLoc
+import GHC.Unit.Module.Warnings
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
+import Ormolu.Utils
 
 p_warnDecls :: WarnDecls GhcPs -> R ()
-p_warnDecls (Warnings NoExtField _ warnings) =
+p_warnDecls (Warnings _ warnings) =
   traverse_ (located' p_warnDecl) warnings
 
 p_warnDecl :: WarnDecl GhcPs -> R ()
-p_warnDecl (Warning NoExtField functions warningTxt) =
-  p_topLevelWarning functions warningTxt
-
-p_moduleWarning :: WarningTxt -> R ()
-p_moduleWarning wtxt = do
-  let (pragmaText, lits) = warningText wtxt
-  inci $ pragma pragmaText $ inci $ p_lits lits
-
-p_topLevelWarning :: [Located RdrName] -> WarningTxt -> R ()
-p_topLevelWarning fnames wtxt = do
+p_warnDecl (Warning (namespace, _) fnames wtxt) = do
   let (pragmaText, lits) = warningText wtxt
-  switchLayout (fmap getLoc fnames ++ fmap getLoc lits) $
+  switchLayout (fmap getLocA fnames ++ fmap getLocA lits) $
     pragma pragmaText . inci $ do
+      p_namespaceSpec namespace
       sep commaDel p_rdrName fnames
       breakpoint
       p_lits lits
 
-warningText :: WarningTxt -> (Text, [Located StringLiteral])
+p_warningTxt :: WarningTxt GhcPs -> R ()
+p_warningTxt wtxt = do
+  let (pragmaText, lits) = warningText wtxt
+  inci $ pragma pragmaText $ inci $ p_lits lits
+
+warningText :: WarningTxt GhcPs -> (Text, [LocatedE StringLiteral])
 warningText = \case
-  WarningTxt _ lits -> ("WARNING", lits)
-  DeprecatedTxt _ lits -> ("DEPRECATED", lits)
+  WarningTxt mcat _ lits -> ("WARNING" <> T.pack cat, fmap hsDocString <$> lits)
+    where
+      cat = case unLoc <$> mcat of
+        Just InWarningCategory {..} ->
+          " in " <> show (showOutputable @WarningCategory (unLoc iwc_wc))
+        Nothing -> ""
+  DeprecatedTxt _ lits -> ("DEPRECATED", fmap hsDocString <$> lits)
 
-p_lits :: [Located StringLiteral] -> R ()
+p_lits :: [LocatedE StringLiteral] -> R ()
 p_lits = \case
   [l] -> atom l
   ls -> brackets N $ sep commaDel atom ls
diff --git a/src/Ormolu/Printer/Meat/ImportExport.hs b/src/Ormolu/Printer/Meat/ImportExport.hs
--- a/src/Ormolu/Printer/Meat/ImportExport.hs
+++ b/src/Ormolu/Printer/Meat/ImportExport.hs
@@ -1,5 +1,7 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Rendering of import and export lists.
@@ -10,27 +12,30 @@
 where
 
 import Control.Monad
-import GHC.Hs.Extension
-import GHC.Hs.ImpExp
+import Data.Choice (pattern Without)
+import Data.Foldable (for_, traverse_)
+import GHC.Hs
 import GHC.LanguageExtensions.Type
+import GHC.Types.PkgQual
 import GHC.Types.SrcLoc
-import GHC.Unit.Types
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
+import Ormolu.Printer.Meat.Declaration.Warning
 import Ormolu.Utils (RelativePos (..), attachRelativePos)
 
 p_hsmodExports :: [LIE GhcPs] -> R ()
-p_hsmodExports [] = do
-  txt "("
-  breakpoint'
-  txt ")"
 p_hsmodExports xs =
   parens N $ do
     layout <- getLayout
     sep
       breakpoint
-      (\(p, l) -> sitcc (located l (p_lie layout p)))
+      (\(p, l) -> sitcc (located (addDocSrcSpan l) (p_lie layout p)))
       (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
+      Nothing -> lie
+      Just (L l' _) -> L (l <> noAnnSrcSpan l') ie
 
 p_hsmodImport :: ImportDecl GhcPs -> R ()
 p_hsmodImport ImportDecl {..} = do
@@ -41,16 +46,24 @@
   space
   when ideclSafe (txt "safe")
   space
+  case ideclLevelSpec of
+    LevelStylePre l -> p_declLevel l
+    _ -> return ()
+  space
   when
     (isImportDeclQualified ideclQualified && not useQualifiedPost)
     (txt "qualified")
   space
   case ideclPkgQual of
-    Nothing -> return ()
-    Just slit -> atom slit
+    NoRawPkgQual -> return ()
+    RawPkgQual slit -> atom slit
   space
   inci $ do
     located ideclName atom
+    space
+    case ideclLevelSpec of
+      LevelStylePost l -> p_declLevel l
+      _ -> return ()
     when
       (isImportDeclQualified ideclQualified && useQualifiedPost)
       (space >> txt "qualified")
@@ -62,13 +75,12 @@
         space
         located l atom
     space
-    case ideclHiding of
-      Nothing -> return ()
-      Just (hiding, _) ->
-        when hiding (txt "hiding")
-    case ideclHiding of
+    case ideclImportList of
       Nothing -> return ()
-      Just (_, L _ xs) -> do
+      Just (hiding, L _ xs) -> do
+        case hiding of
+          Exactly -> pure ()
+          EverythingBut -> txt "hiding"
         breakpoint
         parens N $ do
           layout <- getLayout
@@ -78,33 +90,46 @@
             (attachRelativePos xs)
     newline
 
+p_declLevel :: ImportDeclLevel -> R ()
+p_declLevel = \case
+  ImportDeclSplice -> txt "splice"
+  ImportDeclQuote -> txt "quote"
+
 p_lie :: Layout -> RelativePos -> IE GhcPs -> R ()
 p_lie encLayout relativePos = \case
-  IEVar NoExtField l1 -> do
+  IEVar mwarn l1 exportDoc -> do
+    for_ mwarn $ \warnTxt -> do
+      located warnTxt p_warningTxt
+      breakpoint
     located l1 p_ieWrappedName
     p_comma
-  IEThingAbs NoExtField l1 -> do
+    p_exportDoc exportDoc
+  IEThingAbs _ l1 exportDoc -> do
     located l1 p_ieWrappedName
     p_comma
-  IEThingAll NoExtField l1 -> do
+    p_exportDoc exportDoc
+  IEThingAll _ l1 exportDoc -> do
     located l1 p_ieWrappedName
     space
     txt "(..)"
     p_comma
-  IEThingWith NoExtField l1 w xs _ -> sitcc $ do
-    located l1 p_ieWrappedName
-    breakpoint
-    inci $ do
-      let names :: [R ()]
-          names = located' p_ieWrappedName <$> xs
-      parens N . sep commaDel sitcc $
-        case w of
-          NoIEWildcard -> names
-          IEWildcard n ->
-            let (before, after) = splitAt n names
-             in before ++ [txt ".."] ++ after
-    p_comma
-  IEModuleContents NoExtField l1 -> do
+    p_exportDoc exportDoc
+  IEThingWith _ l1 w xs exportDoc -> do
+    sitcc $ do
+      located l1 p_ieWrappedName
+      breakpoint
+      inci $ do
+        let names :: [R ()]
+            names = located' p_ieWrappedName <$> xs
+        parens N . sep commaDel sitcc $
+          case w of
+            NoIEWildcard -> names
+            IEWildcard n ->
+              let (before, after) = splitAt n names
+               in before ++ [txt ".."] ++ after
+      p_comma
+    p_exportDoc exportDoc
+  IEModuleContents _ l1 -> do
     located l1 p_hsmodName
     p_comma
   IEGroup NoExtField n str -> do
@@ -113,9 +138,9 @@
       FirstPos -> return ()
       MiddlePos -> newline
       LastPos -> newline
-    p_hsDocString (Asterisk n) False (noLoc str)
+    p_hsDoc (Asterisk n) (Without #endNewline) str
   IEDoc NoExtField str ->
-    p_hsDocString Pipe False (noLoc str)
+    p_hsDoc Pipe (Without #endNewline) str
   IEDocNamed NoExtField str -> p_hsDocName str
   where
     p_comma =
@@ -127,3 +152,23 @@
             MiddlePos -> comma
             LastPos -> return ()
         MultiLine -> comma
+
+    -- This is used to support `@since` annotations for (re)exported items. It
+    -- /must/ use caret style comments, see
+    -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/12098 and
+    -- https://github.com/haskell/haddock/issues/1629#issuecomment-1931354411.
+    p_exportDoc :: Maybe (ExportDoc GhcPs) -> R ()
+    p_exportDoc = traverse_ $ \exportDoc -> do
+      breakpoint
+      p_hsDoc Caret (Without #endNewline) exportDoc
+
+ieExportDoc :: IE GhcPs -> Maybe (ExportDoc GhcPs)
+ieExportDoc = \case
+  IEVar _ _ doc -> doc
+  IEThingAbs _ _ doc -> doc
+  IEThingAll _ _ doc -> doc
+  IEThingWith _ _ _ _ doc -> doc
+  IEModuleContents {} -> Nothing
+  IEGroup {} -> Nothing
+  IEDoc {} -> Nothing
+  IEDocNamed {} -> Nothing
diff --git a/src/Ormolu/Printer/Meat/Module.hs b/src/Ormolu/Printer/Meat/Module.hs
--- a/src/Ormolu/Printer/Meat/Module.hs
+++ b/src/Ormolu/Printer/Meat/Module.hs
@@ -1,5 +1,6 @@
-{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Rendering of modules.
@@ -9,13 +10,11 @@
 where
 
 import Control.Monad
-import qualified Data.Text as T
-import GHC.Hs
+import Data.Choice (pattern With)
+import GHC.Hs hiding (comment)
 import GHC.Types.SrcLoc
-import Ormolu.Imports (normalizeImports)
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Pragma
-import Ormolu.Parser.Shebang
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Comments
 import Ormolu.Printer.Meat.Common
@@ -24,25 +23,21 @@
 import Ormolu.Printer.Meat.ImportExport
 import Ormolu.Printer.Meat.Pragma
 
--- | Render a module.
+-- | Render a module-like entity (either a regular module or a backpack
+-- signature).
 p_hsModule ::
   -- | Stack header
-  Maybe (RealLocated Comment) ->
-  -- | Shebangs
-  [Shebang] ->
+  Maybe LComment ->
   -- | Pragmas and the associated comments
-  [([RealLocated Comment], Pragma)] ->
+  [([LComment], Pragma)] ->
   -- | AST to print
-  HsModule ->
+  HsModule GhcPs ->
   R ()
-p_hsModule mstackHeader shebangs pragmas HsModule {..} = do
-  let deprecSpan = maybe [] (\(L s _) -> [s]) hsmodDeprecMessage
-      exportSpans = maybe [] (\(L s _) -> [s]) hsmodExports
+p_hsModule mstackHeader pragmas HsModule {..} = do
+  let XModulePs {..} = hsmodExt
+      deprecSpan = maybe [] (pure . getLocA) hsmodDeprecMessage
+      exportSpans = maybe [] (pure . getLocA) hsmodExports
   switchLayout (deprecSpan <> exportSpans) $ do
-    forM_ shebangs $ \(Shebang x) ->
-      realLocated x $ \shebang -> do
-        txt (T.pack shebang)
-        newline
     forM_ mstackHeader $ \(L spn comment) -> do
       spitCommentNow spn comment
       newline
@@ -53,24 +48,24 @@
       Nothing -> return ()
       Just hsmodName' -> do
         located hsmodName' $ \name -> do
-          forM_ hsmodHaddockModHeader (p_hsDocString Pipe True)
+          forM_ hsmodHaddockModHeader (p_hsDoc Pipe (With #endNewline))
           p_hsmodName name
         breakpoint
         forM_ hsmodDeprecMessage $ \w -> do
-          located' p_moduleWarning w
+          located' p_warningTxt w
           breakpoint
         case hsmodExports of
           Nothing -> return ()
           Just l -> do
-            located l $ \exports -> do
+            encloseLocated l $ \exports -> do
               inci (p_hsmodExports exports)
             breakpoint
         txt "where"
         newline
     newline
-    forM_ (normalizeImports hsmodImports) (located' p_hsmodImport)
+    forM_ hsmodImports (located' p_hsmodImport)
     newline
-    switchLayout (getLoc <$> hsmodDecls) $ do
+    switchLayout (getLocA <$> hsmodDecls) $ do
       p_hsDecls Free hsmodDecls
       newline
       spitRemainingComments
diff --git a/src/Ormolu/Printer/Meat/Pragma.hs b/src/Ormolu/Printer/Meat/Pragma.hs
--- a/src/Ormolu/Printer/Meat/Pragma.hs
+++ b/src/Ormolu/Printer/Meat/Pragma.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TupleSections #-}
 
 -- | Pretty-printing of language pragmas.
 module Ormolu.Printer.Meat.Pragma
@@ -10,13 +9,16 @@
 
 import Control.Monad
 import Data.Char (isUpper)
-import qualified Data.List as L
-import Data.Maybe (listToMaybe)
-import qualified Data.Text as T
+import Data.List qualified as L
+import Data.Set (Set)
+import Data.Set qualified as Set
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Driver.Flags (Language)
 import GHC.Types.SrcLoc
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Pragma (Pragma (..))
-import Ormolu.Printer.Combinators
+import Ormolu.Printer.Combinators hiding (Placement (..))
 import Ormolu.Printer.Comments
 
 -- | Pragma classification.
@@ -38,7 +40,9 @@
 --
 -- See also: <https://github.com/tweag/ormolu/issues/404>
 data LanguagePragmaClass
-  = -- | All other extensions
+  = -- | A pack of extensions like @GHC2021@ or @Haskell2010@
+    ExtensionPack
+  | -- | All other extensions
     Normal
   | -- | Extensions starting with "No"
     Disabling
@@ -47,7 +51,7 @@
   deriving (Eq, Ord)
 
 -- | Print a collection of 'Pragma's with their associated comments.
-p_pragmas :: [([RealLocated Comment], Pragma)] -> R ()
+p_pragmas :: [([LComment], Pragma)] -> R ()
 p_pragmas ps = do
   let prepare = L.sortOn snd . L.nub . concatMap analyze
       analyze = \case
@@ -59,7 +63,7 @@
   forM_ (prepare ps) $ \(cs, (pragmaTy, x)) ->
     p_pragma cs pragmaTy x
 
-p_pragma :: [RealLocated Comment] -> PragmaTy -> String -> R ()
+p_pragma :: [LComment] -> PragmaTy -> Text -> R ()
 p_pragma comments ty x = do
   forM_ comments $ \(L l comment) -> do
     spitCommentNow l comment
@@ -70,22 +74,28 @@
     OptionsGHC -> "OPTIONS_GHC"
     OptionsHaddock -> "OPTIONS_HADDOCK"
   space
-  txt (T.pack x)
+  txt x
   txt " #-}"
   newline
 
 -- | Classify a 'LanguagePragma'.
-classifyLanguagePragma :: String -> LanguagePragmaClass
+classifyLanguagePragma :: Text -> LanguagePragmaClass
 classifyLanguagePragma = \case
+  str | str `Set.member` extensionPacks -> ExtensionPack
   "ImplicitPrelude" -> Final
   "CUSKs" -> Final
   str ->
-    case splitAt 2 str of
+    case T.splitAt 2 str of
       ("No", rest) ->
-        case listToMaybe rest of
+        case T.uncons rest of
           Nothing -> Normal
-          Just x ->
+          Just (x, _) ->
             if isUpper x
               then Disabling
               else Normal
       _ -> Normal
+
+-- | Extension packs, like @GHC2021@ and @Haskell2010@.
+extensionPacks :: Set Text
+extensionPacks =
+  Set.fromList $ T.pack . show <$> [minBound :: Language .. maxBound]
diff --git a/src/Ormolu/Printer/Meat/Type.hs b/src/Ormolu/Printer/Meat/Type.hs
--- a/src/Ormolu/Printer/Meat/Type.hs
+++ b/src/Ormolu/Printer/Meat/Type.hs
@@ -1,67 +1,63 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TypeFamilies #-}
 
 -- | Rendering of types.
 module Ormolu.Printer.Meat.Type
   ( p_hsType,
-    p_hsTypePostDoc,
     hasDocStrings,
     p_hsContext,
+    p_hsContext',
     p_hsTyVarBndr,
     ForAllVisibility (..),
     p_forallBndrs,
-    p_conDeclFields,
+    p_hsConDeclRecFields,
+    p_hsConDeclField,
+    p_hsConDeclFieldWithDoc,
     p_lhsTypeArg,
-    tyVarsToTypes,
-    tyVarsToTyPats,
+    p_hsSigType,
+    p_hsForAllTelescope,
+    p_hsOuterTyVarBndrs,
+    lhsTypeToSigType,
   )
 where
 
-import Data.Data (Data)
-import GHC.Hs.Decls
-import GHC.Hs.Extension
-import GHC.Hs.Type
-import GHC.Types.Basic hiding (isPromoted)
-import GHC.Types.Name.Reader
+import Data.Choice (pattern With)
+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 Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
-import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Value (p_hsSplice, p_stringLit)
+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.Operators
 import Ormolu.Utils
 
 p_hsType :: HsType GhcPs -> R ()
-p_hsType t = p_hsType' (hasDocStrings t) PipeStyle t
-
-p_hsTypePostDoc :: HsType GhcPs -> R ()
-p_hsTypePostDoc t = p_hsType' (hasDocStrings t) CaretStyle t
-
--- | How to render Haddocks associated with a type.
-data TypeDocStyle
-  = PipeStyle
-  | CaretStyle
+p_hsType t = p_hsType' (hasDocStrings t) t
 
-p_hsType' :: Bool -> TypeDocStyle -> HsType GhcPs -> R ()
-p_hsType' multilineArgs docStyle = \case
-  HsForAllTy NoExtField tele t -> do
-    case tele of
-      HsForAllInvis NoExtField bndrs -> p_forallBndrs ForAllInvis p_hsTyVarBndr bndrs
-      HsForAllVis NoExtField bndrs -> p_forallBndrs ForAllVis p_hsTyVarBndr bndrs
+p_hsType' :: Bool -> HsType GhcPs -> R ()
+p_hsType' multilineArgs = \case
+  HsForAllTy _ tele t -> do
+    p_hsForAllTelescope tele
     interArgBreak
-    p_hsTypeR (unLoc t)
-  HsQualTy NoExtField qs t -> do
+    located t p_hsType
+  HsQualTy _ qs t -> do
     located qs p_hsContext
     space
     txt "=>"
     interArgBreak
     case unLoc t of
       HsQualTy {} -> p_hsTypeR (unLoc t)
-      HsFunTy {} -> p_hsTypeR (unLoc t)
+      HsFunTy {} -> located t p_hsType
       _ -> located t p_hsTypeR
-  HsTyVar NoExtField p n -> do
+  HsTyVar _ p n -> do
     case p of
       IsPromoted -> do
         txt "'"
@@ -70,7 +66,7 @@
           _ -> return ()
       NotPromoted -> return ()
     p_rdrName n
-  HsAppTy NoExtField f x -> do
+  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
         -- parameters together.
@@ -79,7 +75,7 @@
             L _ (HsAppTy _ l r) -> gatherArgs l (r : knownArgs)
             _ -> (f', knownArgs)
         (func, args) = gatherArgs f [x]
-    switchLayout (getLoc f : fmap getLoc args) . sitcc $ do
+    switchLayout (getLocA f : fmap getLocA args) . sitcc $ do
       located func p_hsType
       breakpoint
       inci $
@@ -93,111 +89,109 @@
     inci $ do
       txt "@"
       located kd p_hsType
-  HsFunTy NoExtField arrow x y@(L _ y') -> do
+  HsFunTy _ multAnn x y -> do
     located x p_hsType
     space
-    case arrow of
-      HsUnrestrictedArrow _ -> txt "->"
-      HsLinearArrow _ -> txt "%1 ->"
-      HsExplicitMult _ mult -> do
-        txt "%"
-        p_hsTypeR (unLoc mult)
-        space
-        txt "->"
+    p_hsMultAnn (located' p_hsTypeR) multAnn
+    space
+    txt "->"
     interArgBreak
-    case y' of
-      HsFunTy {} -> p_hsTypeR y'
+    case unLoc y of
+      HsFunTy {} -> p_hsTypeR (unLoc y)
       _ -> located y p_hsTypeR
-  HsListTy NoExtField t ->
+  HsListTy _ t ->
     located t (brackets N . p_hsType)
-  HsTupleTy NoExtField tsort xs ->
+  HsTupleTy _ tsort xs ->
     let parens' =
           case tsort of
             HsUnboxedTuple -> parensHash N
-            HsBoxedTuple -> parens N
-            HsConstraintTuple -> parens N
             HsBoxedOrConstraintTuple -> parens N
      in parens' $ sep commaDel (sitcc . located' p_hsType) xs
-  HsSumTy NoExtField xs ->
+  HsSumTy _ xs ->
     parensHash N $
-      sep (txt "|" >> breakpoint) (sitcc . located' p_hsType) xs
-  HsOpTy NoExtField x op y ->
-    sitcc $
-      let opTree = OpBranch (tyOpTree x) op (tyOpTree y)
-       in p_tyOpTree (reassociateOpTree Just opTree)
-  HsParTy NoExtField t ->
-    parens N (located t p_hsType)
-  HsIParamTy NoExtField n t -> sitcc $ do
+      sep (space >> txt "|" >> breakpoint) (sitcc . located' p_hsType) xs
+  HsOpTy _ _ x op y -> do
+    modFixityMap <- askModuleFixityMap
+    debug <- askDebug
+    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) $
+      parens N (located t p_hsType)
+  HsIParamTy _ n t -> sitcc $ do
     located n atom
     space
     txt "::"
     breakpoint
     inci (located t p_hsType)
-  HsStarTy NoExtField _ -> txt "*"
-  HsKindSig NoExtField t k -> sitcc $ do
+  HsStarTy _ _ -> txt "*"
+  HsKindSig _ t k -> sitcc $ do
     located t p_hsType
     space
     txt "::"
     breakpoint
     inci (located k p_hsType)
-  HsSpliceTy NoExtField splice -> p_hsSplice splice
-  HsDocTy NoExtField t str ->
-    case docStyle of
-      PipeStyle -> do
-        p_hsDocString Pipe True str
-        located t p_hsType
-      CaretStyle -> do
-        located t p_hsType
-        newline
-        p_hsDocString Caret False str
-  HsBangTy NoExtField (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 ()
+  HsSpliceTy _ splice -> p_hsUntypedSplice DollarSplice splice
+  HsDocTy _ t str -> do
+    p_hsDoc Pipe (With #endNewline) str
     located t p_hsType
-  HsRecTy NoExtField fields ->
-    p_conDeclFields fields
-  HsExplicitListTy NoExtField p xs -> do
+  HsExplicitListTy _ p xs -> do
     case p of
       IsPromoted -> txt "'"
       NotPromoted -> return ()
     brackets N $ do
-      -- If both this list itself and the first element is promoted,
-      -- we need to put a space in between or it fails to parse.
+      -- If this list is promoted and the first element starts with a single
+      -- quote, we need to put a space in between or it fails to parse.
       case (p, xs) of
-        (IsPromoted, L _ t : _) | isPromoted t -> space
+        (IsPromoted, L _ t : _) | startsWithSingleQuote t -> space
         _ -> return ()
       sep commaDel (sitcc . located' p_hsType) xs
-  HsExplicitTupleTy NoExtField xs -> do
-    txt "'"
+  HsExplicitTupleTy _ p xs -> do
+    case p of
+      IsPromoted -> txt "'"
+      NotPromoted -> return ()
     parens N $ do
-      case xs of
-        L _ t : _ | isPromoted t -> space
+      -- If this tuple is promoted and the first element starts with a single
+      -- quote, we need to put a space in between or it fails to parse.
+      case (p, xs) of
+        (IsPromoted, L _ t : _) | startsWithSingleQuote t -> space
         _ -> return ()
       sep commaDel (located' p_hsType) xs
-  HsTyLit NoExtField t ->
+  HsTyLit _ t ->
     case t of
       HsStrTy (SourceText s) _ -> p_stringLit s
       a -> atom a
-  HsWildCardTy NoExtField -> txt "_"
-  XHsType (NHsCoreTy t) -> atom t
+  HsWildCardTy _ -> txt "_"
+  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
-    isPromoted = \case
-      HsAppTy _ (L _ f) _ -> isPromoted f
+    startsWithSingleQuote = \case
+      HsAppTy _ (L _ f) _ -> startsWithSingleQuote f
       HsTyVar _ IsPromoted _ -> True
       HsExplicitTupleTy {} -> True
       HsExplicitListTy {} -> True
+      HsTyLit _ HsCharTy {} -> True
       _ -> False
     interArgBreak =
       if multilineArgs
         then newline
         else breakpoint
-    p_hsTypeR = p_hsType' multilineArgs docStyle
+    p_hsTypeR = p_hsType' multilineArgs
 
 -- | Return 'True' if at least one argument in 'HsType' has a doc string
 -- attached to it.
@@ -210,41 +204,66 @@
   _ -> False
 
 p_hsContext :: HsContext GhcPs -> R ()
-p_hsContext = \case
+p_hsContext = p_hsContext' p_hsType
+
+p_hsContext' :: (HasLoc (Anno a)) => (a -> R ()) -> [XRec GhcPs a] -> R ()
+p_hsContext' f = \case
   [] -> txt "()"
-  [x] -> located x p_hsType
-  xs -> parens N $ sep commaDel (sitcc . located' p_hsType) xs
+  [x] -> located x f
+  xs -> parens N $ sep commaDel (sitcc . located' f) xs
 
-class IsInferredTyVarBndr flag where
+class IsTyVarBndrFlag flag where
   isInferred :: flag -> Bool
+  p_tyVarBndrFlag :: flag -> R ()
+  p_tyVarBndrFlag _ = pure ()
 
-instance IsInferredTyVarBndr () where
+instance IsTyVarBndrFlag () where
   isInferred () = False
 
-instance IsInferredTyVarBndr Specificity where
+instance IsTyVarBndrFlag Specificity where
   isInferred = \case
     InferredSpec -> True
     SpecifiedSpec -> False
 
-p_hsTyVarBndr :: IsInferredTyVarBndr flag => HsTyVarBndr flag GhcPs -> R ()
-p_hsTyVarBndr = \case
-  UserTyVar NoExtField flag x ->
-    (if isInferred flag then braces N else id) $ p_rdrName x
-  KindedTyVar NoExtField flag l k -> (if isInferred flag then braces else parens) N $ do
-    located l atom
-    space
-    txt "::"
-    breakpoint
-    inci (located k p_hsType)
+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 -> do
+        space
+        txt "::"
+        breakpoint
+        inci (located k p_hsType)
+      HsBndrNoKind _ -> pure ()
+
 data ForAllVisibility = ForAllInvis | ForAllVis
 
 -- | Render several @forall@-ed variables.
-p_forallBndrs :: Data a => ForAllVisibility -> (a -> R ()) -> [Located a] -> R ()
+p_forallBndrs ::
+  (HasLoc l) =>
+  ForAllVisibility ->
+  (a -> R ()) ->
+  [GenLocated l a] ->
+  R ()
 p_forallBndrs ForAllInvis _ [] = txt "forall."
 p_forallBndrs ForAllVis _ [] = txt "forall ->"
 p_forallBndrs vis p tyvars =
-  switchLayout (getLoc <$> tyvars) $ do
+  switchLayout (locA <$> tyvars) $ do
     txt "forall"
     breakpoint
     inci $ do
@@ -253,65 +272,77 @@
         ForAllInvis -> txt "."
         ForAllVis -> space >> txt "->"
 
-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 =
+  braces N $ sep commaDel (sitcc . located' p_hsConDeclRecField) xs
 
-p_conDeclField :: ConDeclField GhcPs -> R ()
-p_conDeclField ConDeclField {..} = do
-  mapM_ (p_hsDocString Pipe True) cd_fld_doc
+p_hsConDeclRecField :: HsConDeclRecField GhcPs -> R ()
+p_hsConDeclRecField HsConDeclRecField {..} = do
+  mapM_ (p_hsDoc Pipe (With #endNewline)) (cdf_doc cdrf_spec)
   sitcc $
     sep
       commaDel
-      (located' (p_rdrName . rdrNameFieldOcc))
-      cd_fld_names
+      (located' (p_rdrName . foLabel))
+      cdrf_names
   space
+  p_hsMultAnn (located' p_hsType) (cdf_multiplicity cdrf_spec)
+  space
   txt "::"
   breakpoint
-  sitcc . inci $ p_hsType (unLoc cd_fld_type)
+  sitcc . inci $ p_hsConDeclField cdrf_spec
 
-tyOpTree :: LHsType GhcPs -> OpTree (LHsType GhcPs) (Located RdrName)
-tyOpTree (L _ (HsOpTy NoExtField l op r)) =
-  OpBranch (tyOpTree l) op (tyOpTree r)
-tyOpTree n = OpNode n
+-- | 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_tyOpTree :: OpTree (LHsType GhcPs) (Located RdrName) -> R ()
-p_tyOpTree (OpNode n) = located n p_hsType
-p_tyOpTree (OpBranch l op r) = do
-  switchLayout [opTreeLoc l] $
-    p_tyOpTree l
-  breakpoint
-  inci . switchLayout [opTreeLoc r] $ do
-    p_rdrName op
-    space
-    p_tyOpTree r
+p_hsConDeclFieldWithDoc :: HsConDeclField GhcPs -> R ()
+p_hsConDeclFieldWithDoc cdf = do
+  mapM_ (p_hsDoc Pipe (With #endNewline)) (cdf_doc cdf)
+  p_hsConDeclField cdf
 
 p_lhsTypeArg :: LHsTypeArg GhcPs -> R ()
 p_lhsTypeArg = \case
-  HsValArg ty -> located ty p_hsType
+  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
   HsTypeArg _ ty -> txt "@" *> located ty p_hsType
   -- NOTE(amesgen) is this unreachable or just not implemented?
   HsArgPar _ -> notImplemented "HsArgPar"
 
-----------------------------------------------------------------------------
--- Conversion functions
+p_hsSigType :: HsSigType GhcPs -> R ()
+p_hsSigType HsSig {..} = do
+  p_hsOuterTyVarBndrs sig_bndrs
+  case sig_bndrs of
+    HsOuterImplicit {} -> pure ()
+    HsOuterExplicit {} -> breakpoint
+  located sig_body p_hsType
 
-tyVarsToTypes :: LHsQTyVars GhcPs -> [LHsType GhcPs]
-tyVarsToTypes HsQTvs {..} = fmap tyVarToType <$> hsq_explicit
+p_hsForAllTelescope :: HsForAllTelescope GhcPs -> R ()
+p_hsForAllTelescope = \case
+  HsForAllInvis _ bndrs -> p_forallBndrs ForAllInvis p_hsTyVarBndr bndrs
+  HsForAllVis _ bndrs -> p_forallBndrs ForAllVis p_hsTyVarBndr bndrs
 
-tyVarToType :: HsTyVarBndr () GhcPs -> HsType GhcPs
-tyVarToType = \case
-  UserTyVar NoExtField () tvar -> HsTyVar NoExtField NotPromoted tvar
-  KindedTyVar NoExtField () tvar kind ->
-    -- Note: we always add parentheses because for whatever reason GHC does
-    -- not use HsParTy for left-hand sides of declarations. Please see
-    -- <https://gitlab.haskell.org/ghc/ghc/issues/17404>. This is fine as
-    -- long as 'tyVarToType' does not get applied to right-hand sides of
-    -- declarations.
-    HsParTy NoExtField . noLoc $
-      HsKindSig NoExtField (noLoc (HsTyVar NoExtField NotPromoted tvar)) kind
+p_hsOuterTyVarBndrs ::
+  HsOuterTyVarBndrs Specificity GhcPs ->
+  R ()
+p_hsOuterTyVarBndrs = \case
+  HsOuterImplicit _ -> pure ()
+  HsOuterExplicit _ bndrs -> p_hsForAllTelescope $ mkHsForAllInvisTele noAnn bndrs
 
-tyVarsToTyPats :: LHsQTyVars GhcPs -> HsTyPats GhcPs
-tyVarsToTyPats HsQTvs {..} = HsValArg . fmap tyVarToType <$> hsq_explicit
+----------------------------------------------------------------------------
+-- Conversion functions
+
+lhsTypeToSigType :: LHsType GhcPs -> LHsSigType GhcPs
+lhsTypeToSigType ty =
+  L (getLoc ty) . HsSig NoExtField (HsOuterImplicit NoExtField) $ ty
diff --git a/src/Ormolu/Printer/Operators.hs b/src/Ormolu/Printer/Operators.hs
--- a/src/Ormolu/Printer/Operators.hs
+++ b/src/Ormolu/Printer/Operators.hs
@@ -1,179 +1,372 @@
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE PatternSynonyms #-}
 
 -- | This module helps handle operator chains composed of different
 -- operators that may have different precedence and fixities.
 module Ormolu.Printer.Operators
   ( OpTree (..),
+    pattern BinaryOpBranches,
+    OpInfo (..),
     opTreeLoc,
     reassociateOpTree,
+    isHardSplitterOp,
   )
 where
 
-import Data.Function (on)
-import qualified Data.List as L
-import Data.Map.Strict (Map)
-import qualified Data.Map.Strict as M
-import Data.Maybe (fromMaybe, mapMaybe)
-import GHC.Types.Basic
-import GHC.Types.Name.Occurrence (occNameString)
+import Data.Choice (Choice)
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.List.NonEmpty qualified as NE
+import GHC.Parser.Annotation
 import GHC.Types.Name.Reader
 import GHC.Types.SrcLoc
-import Ormolu.Utils (unSrcSpan)
+import Ormolu.Fixity
+import Ormolu.Utils
 
--- | Intermediate representation of operator trees. It has two type
--- parameters: @ty@ is the type of sub-expressions, while @op@ is the type
--- of operators.
+-- | 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
+-- operators (n >= 1).
+--
+-- This representation allows us to put all the operators with the same
+-- precedence level as direct siblings in this tree, to better represent the
+-- idea of a chain of operators.
 data OpTree ty op
-  = OpNode ty
-  | OpBranch
-      (OpTree ty op)
-      op
-      (OpTree ty op)
+  = -- | A node which is not an operator application
+    OpNode ty
+  | -- | A subtree of operator application(s); the invariant is: @length
+    -- exprs == length ops + 1@. @OpBranches [x, y, z] [op1, op2]@
+    -- represents the expression @x op1 y op2 z@.
+    OpBranches (NonEmpty (OpTree ty op)) [op]
+  deriving (Eq, Show)
 
+pattern BinaryOpBranches :: OpTree ty op -> op -> OpTree ty op -> OpTree ty op
+pattern BinaryOpBranches x op y = OpBranches (x :| [y]) [op]
+
+-- | Wrapper for an operator, carrying information about its name and
+-- fixity.
+data OpInfo op = OpInfo
+  { -- | The actual operator
+    opiOp :: op,
+    -- | Its name, if available. We use 'Maybe RdrName' here instead of
+    -- 'RdrName' because the name-fetching function received by
+    -- 'reassociateOpTree' returns a 'Maybe'
+    opiName :: Maybe RdrName,
+    -- | Information about the fixity direction and precedence level of the
+    -- operator
+    opiFixityApproximation :: FixityApproximation
+  }
+  deriving (Eq)
+
+-- | Compare the precedence level of two operators. 'OpInfo' is required
+-- (and not just 'FixityInfo') because operator names are used in the case
+-- of equality.
+compareOp :: OpInfo op -> OpInfo op -> Maybe Ordering
+compareOp
+  (OpInfo _ mName1 FixityApproximation {faMinPrecedence = min1, faMaxPrecedence = max1})
+  (OpInfo _ mName2 FixityApproximation {faMinPrecedence = min2, faMaxPrecedence = max2}) =
+    if
+      -- Only declare two precedence levels as equal when
+      --  * either both precedence levels are precise
+      --    (fiMinPrecedence == fiMaxPrecedence) and match
+      --  * or when the precedence levels are imprecise but when the
+      --    operator names match
+      | min1 == min2
+          && max1 == max2
+          && (min1 == max1 || sameSymbol) ->
+          Just EQ
+      | max1 < min2 -> Just LT
+      | max2 < min1 -> Just GT
+      | otherwise -> Nothing
+    where
+      sameSymbol = case (mName1, mName2) of
+        (Just n1, Just n2) -> n1 == n2
+        _ -> False
+
 -- | Return combined 'SrcSpan's of all elements in this 'OpTree'.
-opTreeLoc :: OpTree (Located a) b -> SrcSpan
-opTreeLoc (OpNode (L l _)) = l
-opTreeLoc (OpBranch l _ r) = combineSrcSpans (opTreeLoc l) (opTreeLoc r)
+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 automagically inferred
--- relative precedence of operators. Users are expected to first construct
--- an initial 'OpTree', then re-associate it using this function before
--- printing.
+-- | Re-associate an 'OpTree' taking into account precedence of operators.
+-- Users are expected to first construct an initial 'OpTree', then
+-- re-associate it using this function before printing.
 reassociateOpTree ::
+  -- | Whether to print debug info regarding fixity inference
+  Choice "debug" ->
   -- | How to get name of an operator
   (op -> Maybe RdrName) ->
+  -- | Fixity Map
+  ModuleFixityMap ->
   -- | Original 'OpTree'
-  OpTree (Located ty) (Located op) ->
-  -- | Re-associated 'OpTree'
-  OpTree (Located ty) (Located op)
-reassociateOpTree getOpName opTree =
-  reassociateOpTreeWith
-    (buildFixityMap getOpName normOpTree)
-    (getOpName . unLoc)
-    normOpTree
-  where
-    normOpTree = normalizeOpTree opTree
+  OpTree ty op ->
+  -- | Re-associated 'OpTree', with added context and info around operators
+  OpTree ty (OpInfo op)
+reassociateOpTree debug getOpName modFixityMap =
+  reassociateFlatOpTree
+    . makeFlatOpTree
+    . addFixityInfo debug modFixityMap getOpName
 
--- | Re-associate an 'OpTree' given the map with operator fixities.
-reassociateOpTreeWith ::
-  forall ty op.
+-- | Wrap every operator of the tree with 'OpInfo' to carry the information
+-- about its fixity (extracted from the specified fixity map).
+addFixityInfo ::
+  -- | Whether to print debug info regarding fixity inference
+  Choice "debug" ->
   -- | Fixity map for operators
-  Map String Fixity ->
+  ModuleFixityMap ->
   -- | How to get the name of an operator
   (op -> Maybe RdrName) ->
-  -- | Original 'OpTree'
+  -- | 'OpTree'
   OpTree ty op ->
-  -- | Re-associated 'OpTree'
-  OpTree ty op
-reassociateOpTreeWith fixityMap getOpName = go
+  -- | 'OpTree', with fixity info wrapped around each operator
+  OpTree ty (OpInfo op)
+addFixityInfo _ _ _ (OpNode n) = OpNode n
+addFixityInfo debug modFixityMap getOpName (OpBranches exprs ops) =
+  OpBranches
+    (addFixityInfo debug modFixityMap getOpName <$> exprs)
+    (toOpInfo <$> ops)
   where
-    fixityOf :: op -> Fixity
-    fixityOf op = fromMaybe defaultFixity $ do
-      s <- occNameString . rdrNameOcc <$> getOpName op
-      M.lookup s fixityMap
-    -- Here, left branch is already associated and the root alongside with
-    -- the right branch is right-associated. This function picks up one item
-    -- from the right and inserts it correctly to the left.
-    --
-    -- Also, we are using the 'compareFixity' function which tells if the
-    -- expression should associate to right.
-    go :: OpTree ty op -> OpTree ty op
-    -- base cases
-    go t@(OpNode _) = t
-    go t@(OpBranch (OpNode _) _ (OpNode _)) = t
-    -- shift one operator to the left at the beginning
-    go (OpBranch l@(OpNode _) op (OpBranch l' op' r')) =
-      go (OpBranch (OpBranch l op l') op' r')
-    -- at the last operator, place the operator and don't recurse
-    go (OpBranch (OpBranch l op r) op' r'@(OpNode _)) =
-      if snd $ compareFixity (fixityOf op) (fixityOf op')
-        then OpBranch l op (go $ OpBranch r op' r')
-        else OpBranch (OpBranch l op r) op' r'
-    -- else, shift one operator to left and recurse.
-    go (OpBranch (OpBranch l op r) op' (OpBranch l' op'' r')) =
-      if snd $ compareFixity (fixityOf op) (fixityOf op')
-        then go $ OpBranch (OpBranch l op (go $ OpBranch r op' l')) op'' r'
-        else go $ OpBranch (OpBranch (OpBranch l op r) op' l') op'' r'
+    toOpInfo o = OpInfo o mrdrName fixityApproximation
+      where
+        mrdrName = getOpName o
+        fixityApproximation = case mrdrName of
+          Nothing -> defaultFixityApproximation
+          Just rdrName -> inferFixity debug rdrName modFixityMap
 
--- | A score assigned to an operator.
-data Score
-  = -- | The operator was placed at the beginning of a line
-    AtBeginning Int
-  | -- | The operator was placed at the end of a line
-    AtEnd
-  | -- | The operator was placed in between arguments on a single line
-    InBetween
-  deriving (Eq, Ord)
+-- | Given a '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
+makeFlatOpTree (OpBranches exprs ops) =
+  OpBranches rExprs rOps
+  where
+    makeFlatOpTree' expr = case makeFlatOpTree expr of
+      OpNode n -> (NE.singleton (OpNode n), [])
+      OpBranches noptExprs noptOps -> (noptExprs, noptOps)
+    flattenedSubTrees = makeFlatOpTree' <$> exprs
+    rExprs = fst =<< flattenedSubTrees
+    rOps = concat $ interleave (snd <$> NE.toList flattenedSubTrees) (pure <$> ops)
+    interleave (x : xs) (y : ys) = x : y : interleave xs ys
+    interleave [] ys = ys
+    interleave xs [] = xs
 
--- | Build a map of inferred 'Fixity's from an 'OpTree'.
-buildFixityMap ::
-  forall ty op.
-  -- | How to get the name of an operator
-  (op -> Maybe RdrName) ->
-  -- | Operator tree
-  OpTree (Located ty) (Located op) ->
-  -- | Fixity map
-  Map String Fixity
-buildFixityMap getOpName opTree =
-  addOverrides
-    . M.fromList
-    . concatMap (\(i, ns) -> map (\(n, _) -> (n, fixity i InfixL)) ns)
-    . zip [2 ..]
-    . L.groupBy ((==) `on` snd)
-    . selectScores
-    $ score opTree
+-- | 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).
+--
+-- We have two complementary ways to build the proper sub-trees:
+--
+-- * if we can find a set of operators "minOps" at the current level where
+--     forall (op1, op2) \in minOps x minOps, op1 `equal` op2
+--     forall (op1, op2) \in minOps x (opsOfCurrentLevel \ minOps),
+--       op1 `lessThan` op2
+--   then we can build a subtree with the exprs and ops located "between"
+--   each element of minOps.
+--   For example, if minOps = {op0, op2, op5},
+--   and if [...] means "extract a subtree", then
+--   currentLevel =
+--     [ex0 op0 ex1 op1 ex2 op2 ex3 op3 ex4 op4 ex5 op5 ex6 op6 ex7]
+--   will become
+--     [ex0 op0 [ex1 op1 ex2] op2 [ex3 op3 ex4 op4 ex5] op5 [ex6 op6 ex7]]
+-- * if we can find a set of operators "maxOps" at the current level where
+--     forall (op1, op2) \in maxOps x maxOps, op1 `equal` op2
+--     forall (op1, op2) \in maxOps x (opsOfCurrentLevel \ maxOps),
+--       op1 `greaterThan` op2
+--   then we can build a subtree with every contiguous range of elements
+--   from maxOps (and the exprs on their sides)
+--   For example, if maxOps = {op0, op1, op4},
+--   and if [...] means "extract a subtree", then
+--   currentLevel =
+--     [ex0 op0 ex1 op1 ex2 op2 ex3 op3 ex4 op4 ex5 op5 ex6 op6 ex7]
+--   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.
+reassociateFlatOpTree ::
+  -- | Flat 'OpTree', with fixity info wrapped around each operator
+  OpTree ty (OpInfo op) ->
+  -- | Re-associated 'OpTree', with fixity info wrapped around each operator
+  OpTree ty (OpInfo op)
+reassociateFlatOpTree tree@(OpNode _) = tree
+reassociateFlatOpTree tree@(OpBranches noptExprs noptOps) =
+  case indexOfMinMaxPrecOps noptOps of
+    (Just minIndices, _) -> splitTree noptExprs noptOps minIndices
+    (_, Just maxIndices) -> groupTree noptExprs noptOps maxIndices
+    _ -> case indicesOfHardSplitter of
+      [] -> tree
+      indices -> splitTree noptExprs noptOps indices
   where
-    addOverrides :: Map String Fixity -> Map String Fixity
-    addOverrides m =
-      M.fromList
-        [ ("$", fixity 0 InfixR),
-          (":", fixity 1 InfixR),
-          (".", fixity 100 InfixL)
-        ]
-        `M.union` m
-    fixity = Fixity NoSourceText
-    score :: OpTree (Located ty) (Located op) -> [(String, Score)]
-    score (OpNode _) = []
-    score (OpBranch l o r) = fromMaybe (score r) $ do
-      -- If we fail to get any of these, 'defaultFixity' will be used by
-      -- 'reassociateOpTreeWith'.
-      le <- srcSpanEndLine <$> unSrcSpan (opTreeLoc l) -- left end
-      ob <- srcSpanStartLine <$> unSrcSpan (getLoc o) -- operator begin
-      oe <- srcSpanEndLine <$> unSrcSpan (getLoc o) -- operator end
-      rb <- srcSpanStartLine <$> unSrcSpan (opTreeLoc r) -- right begin
-      oc <- srcSpanStartCol <$> unSrcSpan (getLoc o) -- operator column
-      opName <- occNameString . rdrNameOcc <$> getOpName (unLoc o)
-      let s
-            | le < ob = AtBeginning oc
-            | oe < rb = AtEnd
-            | otherwise = InBetween
-      return $ (opName, s) : score r
-    selectScores :: [(String, Score)] -> [(String, Score)]
-    selectScores =
-      L.sortOn snd
-        . mapMaybe
-          ( \case
-              [] -> Nothing
-              xs@((n, _) : _) -> Just (n, selectScore $ map snd xs)
-          )
-        . L.groupBy ((==) `on` fst)
-        . L.sort
-    selectScore :: [Score] -> Score
-    selectScore xs =
-      case filter (/= InBetween) xs of
-        [] -> InBetween
-        xs' -> maximum xs'
+    indicesOfHardSplitter =
+      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
+        go ::
+          -- Remaining operators to look up
+          [OpInfo op] ->
+          -- Index of the next operator
+          Int ->
+          -- representative of the current minOps set, if there is one,
+          -- or representative of the lowest precedence level encountered
+          -- so far otherwise
+          OpInfo op ->
+          -- indices of the elements of the candidate minOps set,
+          -- if there is any
+          Maybe [Int] ->
+          -- representative of the current maxOps set, if there is one, or
+          -- representative of the highest precedence level encountered
+          -- so far otherwise
+          OpInfo op ->
+          -- indices of the elements of the candidate maxOps set,
+          -- if there is any
+          Maybe [Int] ->
+          -- (indices of minOps elements, indices of maxOps elements)
+          (Maybe [Int], Maybe [Int])
+        go [] _ _ minRes _ maxRes = (reverse <$> minRes, reverse <$> maxRes)
+        go (o : os) i minOpi minRes maxOpi maxRes =
+          let (minOpi', minRes') = case compareOp o minOpi of
+                Just EQ -> (minOpi, (:) i <$> minRes)
+                Just LT -> (o, Just [i])
+                Just GT -> (minOpi, minRes)
+                Nothing -> (combine minOpi o, Nothing)
+              (maxOpi', maxRes') = case compareOp o maxOpi of
+                Just EQ -> (maxOpi, (:) i <$> maxRes)
+                Just LT -> (maxOpi, maxRes)
+                Just GT -> (o, Just [i])
+                Nothing -> (combine maxOpi o, Nothing)
+              -- Merge two potential {min/max}Ops representatives for
+              -- which the comparison gave 'OpUnknown' into a representative
+              -- of the {lowest/highest} precedence level encountered so far
+              combine (OpInfo x _ fix1) (OpInfo _ _ fix2) =
+                OpInfo x Nothing (fix1 <> fix2)
+           in go os (i + 1) minOpi' minRes' maxOpi' maxRes'
+    -- If indices = [0, 2, 5], transform
+    --   [ex0 op0 ex1 op1 ex2 op2 ex3 op3 ex4 op4 ex5 op5 ex6 op6 ex7]
+    -- into
+    --   [ex0 op0 [ex1 op1 ex2] op2 [ex3 op3 ex4 op4 ex5] op5 [ex6 op6 ex7]]
+    splitTree nExprs nOps indices = go (NE.toList nExprs) nOps indices 0 [] [] [] []
+      where
+        go ::
+          -- Remaining exprs to look up
+          [OpTree ty (OpInfo op)] ->
+          -- Remaining ops to look up
+          [OpInfo op] ->
+          -- Remaining list of indices of operators on which to split
+          -- (sorted)
+          [Int] ->
+          -- Index of the next expr/op
+          Int ->
+          -- Bag for exprs for the subtree we are building
+          [OpTree ty (OpInfo op)] ->
+          -- Bag for ops for the subtree we are building
+          [OpInfo op] ->
+          -- Bag for exprs of the result tree
+          [OpTree ty (OpInfo op)] ->
+          -- Bag for ops of the result tree
+          [OpInfo op] ->
+          -- Result tree
+          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
+          -- 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
+          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
+          | i == idx =
+              -- 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
+              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
+          let (ops', subOps') = moveOneIfPossible ops subOps
+           in go xs ops' idxs (i + 1) (x : subExprs) subOps' resExprs resOps
 
-----------------------------------------------------------------------------
--- Helpers
+    -- If indices = [0, 1, 4], transform
+    --   [ex0 op0 ex1 op1 ex2 op2 ex3 op3 ex4 op4 ex5 op5 ex6 op6 ex7]
+    -- into
+    --   [[ex0 op0 ex1 op1 ex2] op2 ex3 op3 [ex4 op4 ex5] op5 ex6 op6 ex7]
+    groupTree nExprs nOps indices = go (NE.toList nExprs) nOps indices 0 [] [] [] []
+      where
+        go ::
+          -- remaining exprs to look up
+          [OpTree ty (OpInfo op)] ->
+          -- remaining ops to look up
+          [OpInfo op] ->
+          -- remaining list of indices of operators on which to group
+          -- (sorted)
+          [Int] ->
+          -- index of the next expr/op
+          Int ->
+          -- bag for exprs for the subtree we are building
+          [OpTree ty (OpInfo op)] ->
+          -- bag for ops for the subtree we are building
+          [OpInfo op] ->
+          -- bag for exprs of the result tree
+          [OpTree ty (OpInfo op)] ->
+          -- bag for ops of the result tree
+          [OpInfo op] ->
+          -- 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
+          -- sub-bags and add the resulting node/subtree to the result bag.
+          -- 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
+           in OpBranches (NE.reverse resExprs') (reverse resOps)
+        go (x : xs) (o : os) (idx : idxs) i subExprs subOps resExprs resOps
+          | i == idx =
+              -- The op we are looking at is one on which we need to group.
+              -- So we just add the current expr and op to the sub-bags.
+              go xs os idxs (i + 1) (x : subExprs) (o : subOps) resExprs 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, but in any case the sub-bags are
+          -- not empty. So we finalize the started group using sub-bags and
+          -- the current expr, to form a subtree which is then added to the
+          -- result bag.
+          let (ops', resOps') = moveOneIfPossible ops resOps
+              resExpr = buildFromSub (x :| subExprs) subOps
+           in go xs ops' idxs (i + 1) [] [] (resExpr : resExprs) resOps'
+        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
+          let (ops', resOps') = moveOneIfPossible ops resOps
+           in go xs ops' idxs (i + 1) [] subOps (x : resExprs) resOps'
 
--- | Convert an 'OpTree' to with all operators having the same fixity and
--- associativity (left infix).
-normalizeOpTree :: OpTree ty op -> OpTree ty op
-normalizeOpTree (OpNode n) =
-  OpNode n
-normalizeOpTree (OpBranch (OpNode l) lop r) =
-  OpBranch (OpNode l) lop (normalizeOpTree r)
-normalizeOpTree (OpBranch (OpBranch l' lop' r') lop r) =
-  normalizeOpTree (OpBranch l' lop' (OpBranch r' lop r))
+    moveOneIfPossible [] bs = ([], bs)
+    moveOneIfPossible (a : as) bs = (as, a : bs)
+
+    buildFromSub subExprs subOps = reassociateFlatOpTree $ case subExprs of
+      -- Do not build a subtree when the potential subtree would have
+      -- 1 expr(s) and 0 op(s)
+      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
+-- operators.
+isHardSplitterOp :: FixityApproximation -> Bool
+isHardSplitterOp = (== FixityApproximation (Just InfixR) 0 0)
diff --git a/src/Ormolu/Printer/SpanStream.hs b/src/Ormolu/Printer/SpanStream.hs
--- a/src/Ormolu/Printer/SpanStream.hs
+++ b/src/Ormolu/Printer/SpanStream.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
 -- | Build span stream from AST.
 module Ormolu.Printer.SpanStream
   ( SpanStream (..),
@@ -8,12 +5,15 @@
   )
 where
 
-import Data.DList (DList)
-import qualified Data.DList as D
 import Data.Data (Data)
-import Data.Generics (everything, ext2Q)
+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
@@ -25,22 +25,25 @@
 -- | Create 'SpanStream' from a data structure containing \"located\"
 -- elements.
 mkSpanStream ::
-  Data a =>
+  (Data a) =>
   -- | Data structure to inspect (AST)
   a ->
   SpanStream
 mkSpanStream a =
   SpanStream
     . sortOn realSrcSpanStart
-    . D.toList
-    $ everything mappend (const mempty `ext2Q` queryLocated) a
+    . toList
+    $ everything mappend (const mempty `ext2Q` queryLocated `ext1Q` queryEpAnn) a
   where
     queryLocated ::
       (Data e0) =>
       GenLocated e0 e1 ->
-      DList RealSrcSpan
+      Seq RealSrcSpan
     queryLocated (L mspn _) =
-      case cast mspn :: Maybe SrcSpan of
-        Nothing -> mempty
-        Just (UnhelpfulSpan _) -> mempty
-        Just (RealSrcSpan spn _) -> D.singleton spn
+      maybe mempty srcSpanToRealSrcSpanSeq (cast mspn :: Maybe SrcSpan)
+
+    queryEpAnn :: EpAnn ann -> Seq RealSrcSpan
+    queryEpAnn = srcSpanToRealSrcSpanSeq . locA
+
+    srcSpanToRealSrcSpanSeq =
+      Seq.fromList . maybeToList . srcSpanToRealSrcSpan
diff --git a/src/Ormolu/Processing/Common.hs b/src/Ormolu/Processing/Common.hs
--- a/src/Ormolu/Processing/Common.hs
+++ b/src/Ormolu/Processing/Common.hs
@@ -1,27 +1,57 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
 
 -- | Common definitions for pre- and post- processing.
 module Ormolu.Processing.Common
-  ( OrmoluState (..),
-    startDisabling,
-    endDisabling,
+  ( removeIndentation,
+    reindent,
+    linesInRegion,
+    intSetToRegions,
   )
 where
 
-import Data.String (IsString (..))
+import Data.Char (isSpace)
+import Data.IntSet (IntSet)
+import Data.IntSet qualified as IntSet
+import Data.Text (Text)
+import Data.Text qualified as T
+import Ormolu.Config
 
--- | Ormolu state.
-data OrmoluState
-  = -- | Enabled
-    OrmoluEnabled
-  | -- | Disabled
-    OrmoluDisabled
-  deriving (Eq, Show)
+-- | Remove indentation from a given 'Text'. Return the input with indentation
+-- removed and the detected indentation level.
+removeIndentation :: Text -> (Text, Int)
+removeIndentation (T.lines -> xs) = (T.unlines (T.drop n <$> xs), n)
+  where
+    n = minimum (getIndent <$> xs)
+    getIndent y =
+      if T.all isSpace y
+        then 0
+        else T.length (T.takeWhile isSpace y)
 
--- | Marker for the beginning of the region where Ormolu should be disabled.
-startDisabling :: IsString s => s
-startDisabling = "{- ORMOLU_DISABLE_START"
+-- | Add indentation to a 'Text'.
+reindent :: Int -> Text -> Text
+reindent i = T.unlines . fmap (T.replicate i " " <>) . T.lines
 
--- | Marker for the end of the region where Ormolu should be disabled.
-endDisabling :: IsString s => s
-endDisabling = "ORMOLU_DISABLE_END -}"
+-- | All lines in the region specified by 'RegionDeltas'.
+linesInRegion :: RegionDeltas -> Text -> Text
+linesInRegion RegionDeltas {..} (T.lines -> ls) = T.unlines middle
+  where
+    (_, nonPrefix) = splitAt regionPrefixLength ls
+    middle = take (length nonPrefix - regionSuffixLength) nonPrefix
+
+-- | Convert a set of line indices into disjoint 'RegionDelta's
+intSetToRegions ::
+  -- | Total number of lines
+  Int ->
+  IntSet ->
+  [RegionDeltas]
+intSetToRegions total (IntSet.toAscList -> indices) =
+  regionIndicesToDeltas total <$> go Nothing indices
+  where
+    go Nothing [] = []
+    go (Just (a, b)) [] = [RegionIndices (Just a) (Just b)]
+    go Nothing (i : is) = go (Just (i, i)) is
+    go (Just (a, b)) (i : is)
+      | b + 1 == i = go (Just (a, i)) is
+      | otherwise = RegionIndices (Just a) (Just b) : go (Just (i, i)) is
diff --git a/src/Ormolu/Processing/Cpp.hs b/src/Ormolu/Processing/Cpp.hs
--- a/src/Ormolu/Processing/Cpp.hs
+++ b/src/Ormolu/Processing/Cpp.hs
@@ -2,69 +2,73 @@
 
 -- | Support for CPP.
 module Ormolu.Processing.Cpp
-  ( State (..),
-    processLine,
-    unmaskLine,
+  ( cppLines,
+    eraseCppLines,
   )
 where
 
-import Control.Monad
-import Data.Char (isSpace)
-import qualified Data.List as L
+import Data.IntSet (IntSet)
+import Data.IntSet qualified as IntSet
 import Data.Maybe (isJust)
-import Data.String
 import Data.Text (Text)
-import qualified Data.Text as T
+import Data.Text qualified as T
 
 -- | State of the CPP processor.
 data State
   = -- | Outside of CPP directives
     Outside
-  | -- | In a conditional expression
-    InConditional
-  | -- | In a continuation (after @\\@)
+  | -- | In a conditional expression, with a positive nesting count
+    InConditional Int
+  | -- | In a continuation (after @\\@), but not in a conditional expression
     InContinuation
   deriving (Eq, Show)
 
--- | Automatically mask the line when needed and update the 'State'.
-processLine :: String -> State -> (String, State)
-processLine line state
-  | for "define " = (masked, state')
-  | for "include " = (masked, state')
-  | for "undef " = (masked, state')
-  | for "ifdef " = (masked, InConditional)
-  | for "ifndef " = (masked, InConditional)
-  | for "if " = (masked, InConditional)
-  | for "else" = (masked, InConditional)
-  | for "elif" = (masked, InConditional)
-  | for "endif" = (masked, state')
-  | otherwise =
-    case state of
-      Outside -> (line, Outside)
-      InConditional -> (masked, InConditional)
-      InContinuation -> (masked, state')
+-- | Return an 'IntSet' containing all lines which are affected by CPP.
+cppLines :: Text -> IntSet
+cppLines input = IntSet.fromAscList $ go Outside (T.lines input `zip` [1 ..])
   where
-    for directive = isJust $ do
-      s <- dropWhile isSpace <$> L.stripPrefix "#" line
-      void (L.stripPrefix directive s)
-    masked = maskLine line
-    state' =
-      if "\\" `L.isSuffixOf` line
-        then InContinuation
-        else Outside
-
--- | Mask the given line.
-maskLine :: String -> String
-maskLine x = maskPrefix ++ x
-
--- | If the given line is masked, unmask it. Otherwise return the line
--- unchanged.
-unmaskLine :: Text -> Text
-unmaskLine x =
-  case T.stripPrefix maskPrefix (T.stripStart x) of
-    Nothing -> x
-    Just x' -> x'
+    go _ [] = []
+    go state ((line, i) : ls)
+      | any for ["define ", "include ", "undef "] =
+          i : go contState ls
+      | any for ["ifdef ", "ifndef ", "if "] =
+          let state' = case state of
+                InConditional nc -> InConditional (nc + 1)
+                _ -> InConditional 1
+           in i : go state' ls
+      | for "endif" =
+          let state' = case state of
+                InConditional nc | nc > 1 -> InConditional (nc - 1)
+                _ -> Outside
+           in i : go state' ls
+      | otherwise =
+          let is = case state of
+                Outside -> []
+                _ -> [i]
+              state' = case state of
+                InContinuation -> contState
+                _ -> state
+           in is <> go state' ls
+      where
+        for directive = isJust $ do
+          s <- T.stripStart <$> T.stripPrefix "#" line
+          T.stripPrefix directive s
+        contState =
+          if "\\" `T.isSuffixOf` line && not inConditional
+            then InContinuation
+            else Outside
+          where
+            inConditional = case state of
+              InConditional {} -> True
+              _ -> False
 
--- | Mask prefix for CPP.
-maskPrefix :: IsString s => s
-maskPrefix = "-- ORMOLU_CPP_MASK"
+-- | Replace all lines affected by CPP with blank lines.
+eraseCppLines :: Text -> Text
+eraseCppLines input =
+  T.unlines . fmap eraseCpp $ T.lines input `zip` [1 ..]
+  where
+    linesToErase = cppLines input
+    eraseCpp (x, i) =
+      if i `IntSet.member` linesToErase
+        then "\n"
+        else x
diff --git a/src/Ormolu/Processing/Postprocess.hs b/src/Ormolu/Processing/Postprocess.hs
deleted file mode 100644
--- a/src/Ormolu/Processing/Postprocess.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ViewPatterns #-}
-
--- | Postprocessing for the results of printing.
-module Ormolu.Processing.Postprocess
-  ( postprocess,
-  )
-where
-
-import Data.Text (Text)
-import qualified Data.Text as T
-import Ormolu.Processing.Common
-import qualified Ormolu.Processing.Cpp as Cpp
-
--- | Postprocess output of the formatter.
-postprocess ::
-  -- | Desired indentation level
-  Int ->
-  -- | Input to process
-  Text ->
-  Text
-postprocess indent =
-  T.unlines
-    . fmap indentLine
-    . fmap Cpp.unmaskLine
-    . filter (not . magicComment)
-    . T.lines
-  where
-    magicComment (T.stripStart -> x) =
-      x == startDisabling || x == endDisabling
-    indentLine x = T.replicate indent " " <> x
diff --git a/src/Ormolu/Processing/Preprocess.hs b/src/Ormolu/Processing/Preprocess.hs
--- a/src/Ormolu/Processing/Preprocess.hs
+++ b/src/Ormolu/Processing/Preprocess.hs
@@ -1,5 +1,5 @@
-{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Preprocessing for input source code.
@@ -9,141 +9,168 @@
 where
 
 import Control.Monad
+import Data.Array as A
+import Data.Bifunctor (bimap)
 import Data.Char (isSpace)
-import qualified Data.List as L
-import Data.Maybe (isJust, maybeToList)
-import GHC.Data.FastString
-import GHC.Types.SrcLoc
+import Data.Function ((&))
+import Data.IntMap (IntMap)
+import Data.IntMap.Strict qualified as IntMap
+import Data.IntSet (IntSet)
+import Data.IntSet qualified as IntSet
+import Data.List qualified as L
+import Data.Maybe (isJust)
+import Data.Text (Text)
+import Data.Text qualified as T
 import Ormolu.Config (RegionDeltas (..))
-import Ormolu.Parser.Shebang (isShebang)
 import Ormolu.Processing.Common
-import qualified Ormolu.Processing.Cpp as Cpp
+import Ormolu.Processing.Cpp
 
--- | Transform given input possibly returning comments extracted from it.
--- This handles LINE pragmas, CPP, shebangs, and the magic comments for
--- enabling\/disabling of Ormolu.
+-- | Preprocess the specified region of the input into raw snippets
+-- and subregions to be formatted.
 preprocess ::
-  -- | File name, just to use in the spans
-  FilePath ->
-  -- | Input to process
-  String ->
-  -- | Region deltas
+  -- | Whether CPP is enabled
+  Bool ->
   RegionDeltas ->
-  -- | Literal prefix, pre-processed input, literal suffix, extra comments
-  (String, String, String, [RealLocated String])
-preprocess path input RegionDeltas {..} =
-  go 1 OrmoluEnabled Cpp.Outside id id regionLines
+  Text ->
+  [Either Text RegionDeltas]
+preprocess cppEnabled region rawInput = rawSnippetsAndRegionsToFormat
   where
-    (prefixLines, otherLines) = splitAt regionPrefixLength (lines input)
-    (regionLines, suffixLines) =
-      let regionLength = length otherLines - regionSuffixLength
-       in splitAt regionLength otherLines
-    go !n ormoluState cppState inputSoFar csSoFar = \case
-      [] ->
-        let input' = unlines (inputSoFar [])
-         in ( unlines prefixLines,
-              case ormoluState of
-                OrmoluEnabled -> input'
-                OrmoluDisabled -> input' ++ endDisabling,
-              unlines suffixLines,
-              csSoFar []
-            )
-      (x : xs) ->
-        let (x', ormoluState', cppState', cs) =
-              processLine path n ormoluState cppState x
-         in go
-              (n + 1)
-              ormoluState'
-              cppState'
-              (inputSoFar . (x' :))
-              (csSoFar . (maybeToList cs ++))
-              xs
+    (linesNotToFormat', replacementLines) = linesNotToFormat cppEnabled region rawInput
+    regionsToFormat =
+      intSetToRegions rawLineLength $
+        IntSet.fromAscList [1 .. rawLineLength] IntSet.\\ linesNotToFormat'
+    regionsNotToFormat = intSetToRegions rawLineLength linesNotToFormat'
+    -- We want to interleave the regionsToFormat and regionsNotToFormat.
+    -- If the first non-formattable region starts at the first line, it is
+    -- the first interleaved region, otherwise, we start with the first
+    -- region to format.
+    interleave' = case regionsNotToFormat of
+      r : _ | regionPrefixLength r == 0 -> interleave
+      _ -> flip interleave
+    rawSnippets = flip linesInRegion updatedInput <$> regionsNotToFormat
+      where
+        updatedInput = T.unlines . fmap updateLine . zip [1 ..] . T.lines $ rawInput
+        updateLine (i, line) = IntMap.findWithDefault line i replacementLines
+    rawSnippetsAndRegionsToFormat =
+      interleave' (Left <$> rawSnippets) (Right <$> regionsToFormat)
+        >>= patchSeparatingBlankLines
+        & 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.
+    -- Empty formattable regions are replaced by a blank line instead.
+    -- Extraneous raw snippets at the start/end are dropped afterwards.
+    patchSeparatingBlankLines = \case
+      Right r@RegionDeltas {..} ->
+        if T.all isSpace (linesInRegion r rawInput)
+          then [blankRawSnippet]
+          else
+            [blankRawSnippet | isBlankLine regionPrefixLength]
+              <> [Right r]
+              <> [blankRawSnippet | isBlankLine (rawLineLength - regionSuffixLength - 1)]
+      Left r -> [Left r]
+      where
+        blankRawSnippet = Left "\n"
+        isBlankLine i = isJust . mfilter (T.all isSpace) $ rawLines !!? i
+    isBlankRawSnippet = \case
+      Left r | T.all isSpace r -> True
+      _ -> False
 
--- | Transform a given line possibly returning a comment extracted from it.
-processLine ::
-  -- | File name, just to use in the spans
-  FilePath ->
-  -- | Line number of this line
-  Int ->
-  -- | Whether Ormolu is currently enabled
-  OrmoluState ->
-  -- | CPP state
-  Cpp.State ->
-  -- | The actual line
-  String ->
-  -- | Adjusted line and possibly a comment extracted from it
-  (String, OrmoluState, Cpp.State, Maybe (RealLocated String))
-processLine path n ormoluState Cpp.Outside line
-  | "{-# LINE" `L.isPrefixOf` line =
-    let (pragma, res) = getPragma line
-        size = length pragma
-        ss = mkRealSrcSpan (mkRealSrcLoc' 1) (mkRealSrcLoc' (size + 1))
-     in (res, ormoluState, Cpp.Outside, Just (L ss pragma))
-  | isOrmoluEnable line =
-    case ormoluState of
-      OrmoluEnabled ->
-        (enableMarker, OrmoluEnabled, Cpp.Outside, Nothing)
-      OrmoluDisabled ->
-        (endDisabling ++ enableMarker, OrmoluEnabled, Cpp.Outside, Nothing)
-  | isOrmoluDisable line =
-    case ormoluState of
-      OrmoluEnabled ->
-        (disableMarker ++ startDisabling, OrmoluDisabled, Cpp.Outside, Nothing)
-      OrmoluDisabled ->
-        (disableMarker, OrmoluDisabled, Cpp.Outside, Nothing)
-  | isShebang line =
-    let ss = mkRealSrcSpan (mkRealSrcLoc' 1) (mkRealSrcLoc' (length line))
-     in ("", ormoluState, Cpp.Outside, Just (L ss line))
-  | otherwise =
-    let (line', cppState') = Cpp.processLine line Cpp.Outside
-     in (line', ormoluState, cppState', Nothing)
+    rawLines = A.listArray (0, length rawLines' - 1) rawLines'
+      where
+        rawLines' = T.lines rawInput
+    rawLineLength = length rawLines
+
+    interleave [] bs = bs
+    interleave (a : as) bs = a : interleave bs as
+
+    xs !!? i = if A.bounds xs `A.inRange` i then Just $ xs A.! i else Nothing
+
+-- | All lines we are not supposed to format, and a set of replacements
+-- for specific lines.
+linesNotToFormat ::
+  -- | Whether CPP is enabled
+  Bool ->
+  RegionDeltas ->
+  Text ->
+  (IntSet, IntMap Text)
+linesNotToFormat cppEnabled region@RegionDeltas {..} input =
+  (unconsidered <> magicDisabled <> otherDisabled, lineUpdates)
   where
-    mkRealSrcLoc' = mkRealSrcLoc (mkFastString path) n
-processLine _ _ ormoluState cppState line =
-  let (line', cppState') = Cpp.processLine line cppState
-   in (line', ormoluState, cppState', Nothing)
+    unconsidered =
+      IntSet.fromAscList $
+        [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]
 
--- | Take a line pragma and output its replacement (where line pragma is
--- replaced with spaces) and the contents of the pragma itself.
-getPragma ::
-  -- | Pragma line to analyze
-  String ->
-  -- | Contents of the pragma and its replacement line
-  (String, String)
-getPragma [] = error "Ormolu.Preprocess.getPragma: input must not be empty"
-getPragma s@(x : xs)
-  | "#-}" `L.isPrefixOf` s = ("#-}", "   " ++ drop 3 s)
-  | otherwise =
-    let (prag, remline) = getPragma xs
-     in (x : prag, ' ' : remline)
+-- | Ormolu state.
+data OrmoluState
+  = -- | Enabled
+    OrmoluEnabled
+  | -- | Disabled
+    OrmoluDisabled
+  deriving (Eq, Show)
 
--- | Canonical enable marker.
-enableMarker :: String
-enableMarker = "{- ORMOLU_ENABLE -}"
+-- | All lines which are disabled by Ormolu's magic comments,
+-- as well as normalizing replacements.
+magicDisabledLines :: Text -> (IntSet, IntMap Text)
+magicDisabledLines input =
+  bimap IntSet.fromAscList IntMap.fromAscList . mconcat $
+    go OrmoluEnabled (T.lines input `zip` [1 ..])
+  where
+    go _ [] = []
+    go state ((line, i) : ls)
+      | Just rest <- isMagicComment ormoluDisable line,
+        state == OrmoluEnabled =
+          ([i], [(i, magicComment ormoluDisable <> rest)]) : go OrmoluDisabled ls
+      | Just rest <- isMagicComment ormoluEnable line,
+        state == OrmoluDisabled =
+          ([i], [(i, magicComment ormoluEnable <> rest)]) : go OrmoluEnabled ls
+      | otherwise = iIfDisabled : go state ls
+      where
+        iIfDisabled = case state of
+          OrmoluDisabled -> ([i], [])
+          OrmoluEnabled -> ([], [])
 
--- | Canonical disable marker.
-disableMarker :: String
-disableMarker = "{- ORMOLU_DISABLE -}"
+-- | All lines which satisfy a predicate.
+linesFiltered :: (Text -> Bool) -> Text -> IntSet
+linesFiltered p =
+  IntSet.fromAscList . fmap snd . filter (p . fst) . (`zip` [1 ..]) . T.lines
 
--- | Return 'True' if the given string is an enabling marker.
-isOrmoluEnable :: String -> Bool
-isOrmoluEnable = magicComment "ORMOLU_ENABLE"
+-- | Lines which contain a shebang.
+shebangLines :: Text -> IntSet
+shebangLines = linesFiltered ("#!" `T.isPrefixOf`)
 
--- | Return 'True' if the given string is a disabling marker.
-isOrmoluDisable :: String -> Bool
-isOrmoluDisable = magicComment "ORMOLU_DISABLE"
+-- | Lines which contain a LINE pragma.
+linePragmaLines :: Text -> IntSet
+linePragmaLines = linesFiltered ("{-# LINE" `T.isPrefixOf`)
 
+-- | Inner text of a magic enabling marker.
+ormoluEnable :: Text
+ormoluEnable = "ORMOLU_ENABLE"
+
+-- | Inner text of a magic disabling marker.
+ormoluDisable :: Text
+ormoluDisable = "ORMOLU_DISABLE"
+
+-- | Creates a magic comment with the given inner text.
+magicComment :: Text -> Text
+magicComment t = "{- " <> t <> " -}"
+
 -- | Construct a function for whitespace-insensitive matching of string.
-magicComment ::
+isMagicComment ::
   -- | What to expect
-  String ->
+  Text ->
   -- | String to test
-  String ->
-  -- | Whether or not the two strings watch
-  Bool
-magicComment expected s0 = isJust $ do
-  let trim = dropWhile isSpace
-  s1 <- trim <$> L.stripPrefix "{-" (trim s0)
-  s2 <- trim <$> L.stripPrefix expected s1
-  s3 <- L.stripPrefix "-}" s2
-  guard (all isSpace s3)
+  Text ->
+  -- | If the two strings match, we return the rest of the line.
+  Maybe Text
+isMagicComment expected s0 = do
+  s1 <- T.stripStart <$> T.stripPrefix "{-" (T.stripStart s0)
+  s2 <- T.stripStart <$> T.stripPrefix expected s1
+  T.stripPrefix "-}" s2
diff --git a/src/Ormolu/Terminal.hs b/src/Ormolu/Terminal.hs
--- a/src/Ormolu/Terminal.hs
+++ b/src/Ormolu/Terminal.hs
@@ -1,13 +1,13 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
 
 -- | An abstraction for colorful output in terminal.
 module Ormolu.Terminal
-  ( -- * The 'Term' monad
+  ( -- * The 'Term' abstraction
     Term,
     ColorMode (..),
     runTerm,
+    runTermPure,
 
     -- * Styling
     bold,
@@ -17,109 +17,114 @@
 
     -- * Printing
     put,
-    putS,
-    putSrcSpan,
+    putShow,
+    putOutputable,
     newline,
   )
 where
 
-import Control.Monad.Reader
+import Control.Applicative (Const (..))
+import Control.Monad (forM_)
+import Data.Foldable (toList)
+import Data.Sequence (Seq)
+import Data.Sequence qualified as Seq
 import Data.Text (Text)
-import qualified Data.Text.IO as T
-import GHC.Types.SrcLoc
+import Data.Text qualified as T
+import Data.Text.IO.Utf8 qualified as T.Utf8
+import GHC.Utils.Outputable (Outputable)
 import Ormolu.Utils (showOutputable)
 import System.Console.ANSI
-import System.IO (Handle, hFlush, hPutStr)
+import System.IO (Handle, hFlush)
 
 ----------------------------------------------------------------------------
--- The 'Term' monad
+-- The 'Term' abstraction
 
--- | Terminal monad.
-newtype Term a = Term (ReaderT RC IO a)
-  deriving (Functor, Applicative, Monad)
+type Term = TermOutput ()
 
--- | Reader context of 'Term'.
-data RC = RC
-  { -- | Whether to use colors
-    rcUseColor :: Bool,
-    -- | Handle to print to
-    rcHandle :: Handle
-  }
+newtype TermOutput a = TermOutput (Const (Seq TermOutputNode) a)
+  deriving (Semigroup, Monoid, Functor, Applicative)
 
+data TermOutputNode
+  = OutputText Text
+  | WithColor Color Term
+  | WithBold Term
+
+singleTerm :: TermOutputNode -> Term
+singleTerm = TermOutput . Const . Seq.singleton
+
 -- | Whether to use colors and other features of ANSI terminals.
 data ColorMode = Never | Always | Auto
   deriving (Eq, Show)
 
 -- | Run 'Term' monad.
 runTerm ::
-  -- | Monad to run
-  Term a ->
+  Term ->
   -- | Color mode
   ColorMode ->
   -- | Handle to print to
   Handle ->
-  IO a
-runTerm (Term m) colorMode rcHandle = do
-  rcUseColor <- case colorMode of
+  IO ()
+runTerm term0 colorMode handle = do
+  useSGR <- case colorMode of
     Never -> return False
     Always -> return True
-    Auto -> hSupportsANSI rcHandle
-  x <- runReaderT m RC {..}
-  hFlush rcHandle
-  return x
+    Auto -> hSupportsANSI handle
+  runTerm' useSGR term0
+  hFlush handle
+  where
+    runTerm' useSGR = go
+      where
+        go (TermOutput (Const nodes)) =
+          forM_ nodes $ \case
+            OutputText s -> T.Utf8.hPutStr handle s
+            WithColor color term -> withSGR [SetColor Foreground Dull color] (go term)
+            WithBold term -> withSGR [SetConsoleIntensity BoldIntensity] (go term)
 
+        withSGR sgrs m
+          | useSGR = hSetSGR handle sgrs >> m >> hSetSGR handle [Reset]
+          | otherwise = m
+
+runTermPure :: Term -> Text
+runTermPure (TermOutput (Const nodes)) =
+  T.concat . toList . flip fmap nodes $ \case
+    OutputText s -> s
+    WithColor _ term -> runTermPure term
+    WithBold term -> runTermPure term
+
 ----------------------------------------------------------------------------
 -- Styling
 
--- | Make the inner computation output bold text.
-bold :: Term a -> Term a
-bold = withSGR [SetConsoleIntensity BoldIntensity]
-
--- | Make the inner computation output cyan text.
-cyan :: Term a -> Term a
-cyan = withSGR [SetColor Foreground Dull Cyan]
+-- | Make the output bold text.
+bold :: Term -> Term
+bold = singleTerm . WithBold
 
--- | Make the inner computation output green text.
-green :: Term a -> Term a
-green = withSGR [SetColor Foreground Dull Green]
+-- | Make the output cyan text.
+cyan :: Term -> Term
+cyan = singleTerm . WithColor Cyan
 
--- | Make the inner computation output red text.
-red :: Term a -> Term a
-red = withSGR [SetColor Foreground Dull Red]
+-- | Make the output green text.
+green :: Term -> Term
+green = singleTerm . WithColor Green
 
--- | Alter 'SGR' for inner computation.
-withSGR :: [SGR] -> Term a -> Term a
-withSGR sgrs (Term m) = Term $ do
-  RC {..} <- ask
-  if rcUseColor
-    then do
-      liftIO $ hSetSGR rcHandle sgrs
-      x <- m
-      liftIO $ hSetSGR rcHandle [Reset]
-      return x
-    else m
+-- | Make the output red text.
+red :: Term -> Term
+red = singleTerm . WithColor Red
 
 ----------------------------------------------------------------------------
 -- Printing
 
 -- | Output 'Text'.
-put :: Text -> Term ()
-put txt = Term $ do
-  RC {..} <- ask
-  liftIO $ T.hPutStr rcHandle txt
+put :: Text -> Term
+put = singleTerm . OutputText
 
--- | Output 'String'.
-putS :: String -> Term ()
-putS str = Term $ do
-  RC {..} <- ask
-  liftIO $ hPutStr rcHandle str
+-- | Output a 'Show' value.
+putShow :: (Show a) => a -> Term
+putShow = put . T.pack . show
 
--- | Output a 'GHC.SrcSpan'.
-putSrcSpan :: SrcSpan -> Term ()
-putSrcSpan = putS . showOutputable
+-- | Output an 'Outputable' value.
+putOutputable :: (Outputable a) => a -> Term
+putOutputable = put . T.pack . showOutputable
 
 -- | Output a newline.
-newline :: Term ()
-newline = Term $ do
-  RC {..} <- ask
-  liftIO $ T.hPutStr rcHandle "\n"
+newline :: Term
+newline = put "\n"
diff --git a/src/Ormolu/Terminal/QualifiedDo.hs b/src/Ormolu/Terminal/QualifiedDo.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Terminal/QualifiedDo.hs
@@ -0,0 +1,7 @@
+module Ormolu.Terminal.QualifiedDo ((>>)) where
+
+import Ormolu.Terminal
+import Prelude hiding ((>>))
+
+(>>) :: Term -> Term -> Term
+(>>) = (<>)
diff --git a/src/Ormolu/Utils.hs b/src/Ormolu/Utils.hs
--- a/src/Ormolu/Utils.hs
+++ b/src/Ormolu/Utils.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ViewPatterns #-}
 
 -- | Random utilities used by the code.
 module Ormolu.Utils
@@ -10,26 +9,35 @@
     notImplemented,
     showOutputable,
     splitDocString,
-    unSrcSpan,
     incSpanLine,
     separatedByBlank,
     separatedByBlankNE,
     onTheSameLine,
-    removeIndentation,
+    textToStringBuffer,
+    ghcModuleNameToCabal,
   )
 where
 
-import Data.Char (isSpace)
 import Data.List (dropWhileEnd)
 import Data.List.NonEmpty (NonEmpty (..))
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe (fromMaybe)
 import Data.Text (Text)
-import qualified Data.Text as T
+import Data.Text qualified as T
+import Data.Text.Foreign qualified as TFFI
+import Distribution.ModuleName (ModuleName)
+import Distribution.ModuleName qualified as ModuleName
+import Foreign (pokeElemOff, withForeignPtr)
+import GHC.Data.Strict qualified as Strict
+import GHC.Data.StringBuffer (StringBuffer (..))
+import GHC.Driver.Ppr
 import GHC.DynFlags (baseDynFlags)
-import GHC.Hs
+import GHC.ForeignPtr (mallocPlainForeignPtrBytes)
+import GHC.Hs hiding (ModuleName)
+import GHC.IO.Unsafe (unsafePerformIO)
 import GHC.Types.SrcLoc
-import qualified GHC.Utils.Outputable as GHC
+import GHC.Utils.Outputable (Outputable (..))
+import Language.Haskell.Syntax.Module.Name qualified as GHC
 
 -- | Relative positions in a list.
 data RelativePos
@@ -59,8 +67,8 @@
 notImplemented msg = error $ "not implemented yet: " ++ msg
 
 -- | Pretty-print an 'GHC.Outputable' thing.
-showOutputable :: GHC.Outputable o => o -> String
-showOutputable = GHC.showSDoc baseDynFlags . GHC.ppr
+showOutputable :: (Outputable o) => o -> String
+showOutputable = showSDoc baseDynFlags . ppr
 
 -- | Split and normalize a doc string. The result is a list of lines that
 -- make up the comment.
@@ -76,7 +84,7 @@
         . dropWhileEnd T.null
         . fmap (T.stripEnd . T.pack)
         . lines
-        $ unpackHDS docStr
+        $ 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
     -- syntax @-- $name@ somehow).
@@ -99,12 +107,6 @@
                 then dropSpace <$> xs
                 else xs
 
--- | Get 'RealSrcSpan' out of 'SrcSpan' if the span is “helpful”.
-unSrcSpan :: SrcSpan -> Maybe RealSrcSpan
-unSrcSpan = \case
-  RealSrcSpan r _ -> Just r
-  UnhelpfulSpan _ -> Nothing
-
 -- | Increment line number in a 'SrcSpan'.
 incSpanLine :: Int -> SrcSpan -> SrcSpan
 incSpanLine i = \case
@@ -116,15 +118,15 @@
               line = srcLocLine x
               col = srcLocCol x
            in mkRealSrcLoc file (line + i) col
-     in RealSrcSpan (mkRealSrcSpan (incLine start) (incLine end)) Nothing
+     in RealSrcSpan (mkRealSrcSpan (incLine start) (incLine end)) Strict.Nothing
   UnhelpfulSpan x -> UnhelpfulSpan x
 
 -- | Do two declarations have a blank between them?
 separatedByBlank :: (a -> SrcSpan) -> a -> a -> Bool
 separatedByBlank loc a b =
   fromMaybe False $ do
-    endA <- srcSpanEndLine <$> unSrcSpan (loc a)
-    startB <- srcSpanStartLine <$> unSrcSpan (loc b)
+    endA <- srcSpanEndLine <$> srcSpanToRealSrcSpan (loc a)
+    startB <- srcSpanStartLine <$> srcSpanToRealSrcSpan (loc b)
     pure (startB - endA >= 2)
 
 -- | Do two declaration groups have a blank between them?
@@ -136,13 +138,20 @@
 onTheSameLine a b =
   isOneLineSpan (mkSrcSpan (srcSpanEnd a) (srcSpanStart b))
 
--- | Remove indentation from a given 'String'. Return the input with
--- indentation removed and the detected indentation level.
-removeIndentation :: String -> (String, Int)
-removeIndentation (lines -> xs) = (unlines (drop n <$> xs), n)
+-- | Convert 'Text' to a 'StringBuffer' by making a copy.
+textToStringBuffer :: Text -> StringBuffer
+textToStringBuffer txt = unsafePerformIO $ do
+  buf <- mallocPlainForeignPtrBytes (len + 3)
+  withForeignPtr buf $ \ptr -> do
+    TFFI.unsafeCopyToPtr txt ptr
+    -- last three bytes have to be zero for easier decoding
+    pokeElemOff ptr len 0
+    pokeElemOff ptr (len + 1) 0
+    pokeElemOff ptr (len + 2) 0
+  pure StringBuffer {buf, len, cur = 0}
   where
-    n = minimum (getIndent <$> xs)
-    getIndent y =
-      if all isSpace y
-        then 0
-        else length (takeWhile isSpace y)
+    len = TFFI.lengthWord8 txt
+
+-- | Convert GHC's 'ModuleName' into the one used by Cabal.
+ghcModuleNameToCabal :: GHC.ModuleName -> ModuleName
+ghcModuleNameToCabal = ModuleName.fromString . GHC.moduleNameString
diff --git a/src/Ormolu/Utils/Cabal.hs b/src/Ormolu/Utils/Cabal.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Utils/Cabal.hs
@@ -0,0 +1,205 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Ormolu.Utils.Cabal
+  ( CabalSearchResult (..),
+    CabalInfo (..),
+    Extension (..),
+    getCabalInfoForSourceFile,
+    findCabalFile,
+    parseCabalInfo,
+  )
+where
+
+import Control.Exception
+import Control.Monad.IO.Class
+import Data.ByteString qualified as B
+import Data.Map.Lazy (Map)
+import Data.Map.Lazy qualified as M
+import Data.Maybe (maybeToList)
+import Data.Set (Set)
+import Data.Set qualified as Set
+import Distribution.ModuleName qualified as ModuleName
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parsec
+import Distribution.Types.CondTree qualified as CT
+import Distribution.Utils.Path (getSymbolicPath)
+import Language.Haskell.Extension
+import Ormolu.Config
+import Ormolu.Exception
+import Ormolu.Fixity
+import Ormolu.Utils.IO (Cache, findClosestFileSatisfying, newCache, withCache)
+import System.Directory
+import System.FilePath
+import System.IO.Unsafe (unsafePerformIO)
+
+-- | The result of searching for a @.cabal@ file.
+--
+-- @since 0.5.3.0
+data CabalSearchResult
+  = -- | Cabal file could not be found
+    CabalNotFound
+  | -- | Cabal file was found, but it did not mention the source file in
+    -- question
+    CabalDidNotMention CabalInfo
+  | -- | Cabal file was found and it mentions the source file in question
+    CabalFound CabalInfo
+  deriving (Eq, Show)
+
+-- | Cabal information of interest to Ormolu.
+data CabalInfo = CabalInfo
+  { -- | Package name
+    ciPackageName :: !PackageName,
+    -- | Extension and language settings in the form of 'DynOption's
+    ciDynOpts :: ![DynOption],
+    -- | Direct dependencies
+    ciDependencies :: !(Set PackageName),
+    -- | Absolute path to the cabal file
+    ciCabalFilePath :: !FilePath
+  }
+  deriving (Eq, Show)
+
+-- | Locate a @.cabal@ file corresponding to the given Haskell source file
+-- and obtain 'CabalInfo' from it.
+getCabalInfoForSourceFile ::
+  (MonadIO m) =>
+  -- | Haskell source file
+  FilePath ->
+  -- | Extracted cabal info, if any
+  m CabalSearchResult
+getCabalInfoForSourceFile sourceFile =
+  liftIO (findCabalFile sourceFile) >>= \case
+    Just cabalFile -> do
+      (mentioned, cabalInfo) <- parseCabalInfo cabalFile sourceFile
+      return
+        ( if mentioned
+            then CabalFound cabalInfo
+            else CabalDidNotMention cabalInfo
+        )
+    Nothing -> return CabalNotFound
+
+-- | Find the path to an appropriate @.cabal@ file for a Haskell source
+-- file, if available.
+findCabalFile ::
+  (MonadIO m) =>
+  -- | Path to a Haskell source file in a project with a @.cabal@ file
+  FilePath ->
+  -- | Absolute path to the @.cabal@ file, if available
+  m (Maybe FilePath)
+findCabalFile = findClosestFileSatisfying $ \x ->
+  takeExtension x == ".cabal"
+
+-- | Parsed cabal file information to be shared across multiple source files.
+data CachedCabalFile = CachedCabalFile
+  { -- | Parsed generic package description.
+    genericPackageDescription :: GenericPackageDescription,
+    -- | Map from Haskell source file paths (without any extensions) to the
+    -- corresponding 'DynOption's and dependencies.
+    extensionsAndDeps :: Map FilePath ([DynOption], [PackageName])
+  }
+  deriving (Show)
+
+-- | Cache ref that stores 'CachedCabalFile' per Cabal file.
+cacheRef :: Cache FilePath CachedCabalFile
+cacheRef = unsafePerformIO newCache
+{-# NOINLINE cacheRef #-}
+
+-- | Parse 'CabalInfo' from a @.cabal@ file at the given 'FilePath'.
+parseCabalInfo ::
+  (MonadIO m) =>
+  -- | Location of the .cabal file
+  FilePath ->
+  -- | Location of the source file we are formatting
+  FilePath ->
+  -- | Indication if the source file was mentioned in the Cabal file and the
+  -- extracted 'CabalInfo'
+  m (Bool, CabalInfo)
+parseCabalInfo cabalFileAsGiven sourceFileAsGiven = liftIO $ do
+  cabalFile <- makeAbsolute cabalFileAsGiven
+  sourceFileAbs <- makeAbsolute sourceFileAsGiven
+  CachedCabalFile {..} <- withCache cacheRef cabalFile $ do
+    cabalFileBs <- B.readFile cabalFile
+    genericPackageDescription <-
+      whenLeft (snd . runParseResult $ parseGenericPackageDescription cabalFileBs) $
+        throwIO . OrmoluCabalFileParsingFailed cabalFile . snd
+    let extensionsAndDeps =
+          getExtensionAndDepsMap cabalFile genericPackageDescription
+    pure CachedCabalFile {..}
+  let (dynOpts, dependencies, mentioned) =
+        case M.lookup (dropExtensions sourceFileAbs) extensionsAndDeps of
+          Nothing -> ([], Set.toList defaultDependencies, False)
+          Just (dynOpts', dependencies') -> (dynOpts', dependencies', True)
+      pdesc = packageDescription genericPackageDescription
+  return
+    ( mentioned,
+      CabalInfo
+        { ciPackageName = pkgName (package pdesc),
+          ciDynOpts = dynOpts,
+          ciDependencies = Set.fromList dependencies,
+          ciCabalFilePath = cabalFile
+        }
+    )
+  where
+    whenLeft :: (Applicative f) => Either e a -> (e -> f a) -> f a
+    whenLeft eitha ma = either ma pure eitha
+
+-- | Get a map from Haskell source file paths (without any extensions) to
+-- the corresponding 'DynOption's and dependencies.
+getExtensionAndDepsMap ::
+  -- | Path to the cabal file
+  FilePath ->
+  -- | Parsed generic package description
+  GenericPackageDescription ->
+  Map FilePath ([DynOption], [PackageName])
+getExtensionAndDepsMap cabalFile GenericPackageDescription {..} =
+  M.unions . concat $
+    [ buildMap extractFromLibrary <$> lib ++ sublibs,
+      buildMap extractFromExecutable . snd <$> condExecutables,
+      buildMap extractFromTestSuite . snd <$> condTestSuites,
+      buildMap extractFromBenchmark . snd <$> condBenchmarks
+    ]
+  where
+    lib = maybeToList condLibrary
+    sublibs = snd <$> condSubLibraries
+
+    buildMap f a = M.fromList ((,extsAndDeps) <$> files)
+      where
+        (mergedA, _) = CT.ignoreConditions a
+        (files, extsAndDeps) = f mergedA
+
+    extractFromBuildInfo extraModules BuildInfo {..} = (,(exts, deps)) $ do
+      m <- extraModules ++ (ModuleName.toFilePath <$> otherModules)
+      normalise . (takeDirectory cabalFile </>) <$> prependSrcDirs (dropExtensions m)
+      where
+        prependSrcDirs f
+          | null hsSourceDirs = [f]
+          | otherwise = (</> f) . getSymbolicPath <$> hsSourceDirs
+        deps = depPkgName <$> targetBuildDepends
+        exts = maybe [] langExt defaultLanguage ++ fmap extToDynOption defaultExtensions
+        langExt =
+          pure . DynOption . ("-X" <>) . \case
+            UnknownLanguage lan -> lan
+            lan -> show lan
+        extToDynOption =
+          DynOption . \case
+            EnableExtension e -> "-X" ++ show e
+            DisableExtension e -> "-XNo" ++ show e
+            UnknownExtension e -> "-X" ++ e
+
+    extractFromLibrary Library {..} =
+      extractFromBuildInfo (ModuleName.toFilePath <$> exposedModules) libBuildInfo
+    extractFromExecutable Executable {..} =
+      extractFromBuildInfo [getSymbolicPath modulePath] buildInfo
+    extractFromTestSuite TestSuite {..} =
+      extractFromBuildInfo mainPath testBuildInfo
+      where
+        mainPath = case testInterface of
+          TestSuiteExeV10 _ p -> [getSymbolicPath p]
+          TestSuiteLibV09 _ p -> [ModuleName.toFilePath p]
+          TestSuiteUnsupported {} -> []
+    extractFromBenchmark Benchmark {..} =
+      extractFromBuildInfo mainPath benchmarkBuildInfo
+      where
+        mainPath = case benchmarkInterface of
+          BenchmarkExeV10 _ p -> [getSymbolicPath p]
+          BenchmarkUnsupported {} -> []
diff --git a/src/Ormolu/Utils/Fixity.hs b/src/Ormolu/Utils/Fixity.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Utils/Fixity.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE LambdaCase #-}
+
+module Ormolu.Utils.Fixity
+  ( getDotOrmoluForSourceFile,
+    parseFixityDeclarationStr,
+    parseModuleReexportDeclarationStr,
+  )
+where
+
+import Control.Exception (throwIO)
+import Control.Monad.IO.Class
+import Data.Bifunctor (first)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Text qualified as T
+import Data.Text.IO.Utf8 qualified as T.Utf8
+import Distribution.ModuleName (ModuleName)
+import Distribution.Types.PackageName (PackageName)
+import Ormolu.Exception
+import Ormolu.Fixity
+import Ormolu.Fixity.Parser
+import Ormolu.Utils.IO (Cache, findClosestFileSatisfying, newCache, withCache)
+import System.Directory
+import System.IO.Unsafe (unsafePerformIO)
+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.
+getDotOrmoluForSourceFile ::
+  (MonadIO m) =>
+  -- | 'CabalInfo' already obtained for this source file
+  FilePath ->
+  m (FixityOverrides, ModuleReexports)
+getDotOrmoluForSourceFile sourceFile =
+  liftIO (findDotOrmoluFile sourceFile) >>= \case
+    Just dotOrmoluFile -> liftIO $ withCache cacheRef dotOrmoluFile $ do
+      dotOrmoluRelative <- makeRelativeToCurrentDirectory dotOrmoluFile
+      contents <- T.Utf8.readFile dotOrmoluFile
+      case parseDotOrmolu dotOrmoluRelative contents of
+        Left errorBundle ->
+          throwIO (OrmoluFixityOverridesParseError errorBundle)
+        Right x -> return x
+    Nothing -> return (defaultFixityOverrides, defaultModuleReexports)
+
+-- | Find the path to an appropriate @.ormolu@ file for a Haskell source
+-- file, if available.
+findDotOrmoluFile ::
+  (MonadIO m) =>
+  -- | Path to a Haskell source file
+  FilePath ->
+  -- | Absolute path to the closest @.ormolu@ file, if available
+  m (Maybe FilePath)
+findDotOrmoluFile = findClosestFileSatisfying $ \x ->
+  x == ".ormolu"
+
+-- | Cache ref that maps names of @.ormolu@ files to their contents.
+cacheRef :: Cache FilePath (FixityOverrides, ModuleReexports)
+cacheRef = unsafePerformIO newCache
+{-# NOINLINE cacheRef #-}
+
+-- | A wrapper around 'parseFixityDeclaration' for parsing individual fixity
+-- definitions.
+parseFixityDeclarationStr ::
+  -- | Input to parse
+  String ->
+  -- | Parse result
+  Either String [(OpName, FixityInfo)]
+parseFixityDeclarationStr =
+  first errorBundlePretty . parseFixityDeclaration . T.pack
+
+-- | A wrapper around 'parseModuleReexportDeclaration' for parsing
+-- a individual module reexport.
+parseModuleReexportDeclarationStr ::
+  -- | Input to parse
+  String ->
+  -- | Parse result
+  Either String (ModuleName, NonEmpty (Maybe PackageName, ModuleName))
+parseModuleReexportDeclarationStr =
+  first errorBundlePretty . parseModuleReexportDeclaration . T.pack
diff --git a/src/Ormolu/Utils/IO.hs b/src/Ormolu/Utils/IO.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Utils/IO.hs
@@ -0,0 +1,70 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Ormolu.Utils.IO
+  ( findClosestFileSatisfying,
+    Cache,
+    newCache,
+    withCache,
+  )
+where
+
+import Control.Exception (catch, throwIO)
+import Control.Monad.IO.Class
+import Data.IORef
+import Data.Map.Lazy (Map)
+import Data.Map.Lazy qualified as M
+import System.Directory
+import System.FilePath
+import System.IO.Error (isDoesNotExistError)
+
+-- | Find the path to the closest file higher in the file hierarchy that
+-- satisfies a given predicate.
+findClosestFileSatisfying ::
+  (MonadIO m) =>
+  -- | The predicate that determines what we are looking for
+  (FilePath -> Bool) ->
+  -- | Path to the starting point for the search
+  FilePath ->
+  -- | Absolute path to the found file if available
+  m (Maybe FilePath)
+findClosestFileSatisfying isRightFile rootOfSearch = liftIO $ do
+  parentDir <- takeDirectory <$> makeAbsolute rootOfSearch
+  dirEntries <-
+    listDirectory parentDir `catch` \case
+      (isDoesNotExistError -> True) -> pure []
+      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
+
+newtype Cache k v = Cache (IORef (Map k v))
+
+newCache :: (Ord k) => IO (Cache k v)
+newCache = do
+  var <- newIORef mempty
+  pure (Cache var)
+
+-- | Execute an 'IO' action but only if the given key is not found in the
+-- cache.
+withCache :: (Ord k) => Cache k v -> k -> IO v -> IO v
+withCache (Cache cacheVar) k action = do
+  cache <- readIORef cacheVar
+  case M.lookup k cache of
+    Just v -> pure v
+    Nothing -> do
+      v <- action
+      atomicModifyIORef cacheVar ((,()) . M.insert k v)
+      pure v
diff --git a/tests/Ormolu/CabalInfoSpec.hs b/tests/Ormolu/CabalInfoSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/CabalInfoSpec.hs
@@ -0,0 +1,66 @@
+{-# LANGUAGE RecordWildCards #-}
+
+module Ormolu.CabalInfoSpec (spec) where
+
+import Data.Set qualified as Set
+import Distribution.Types.PackageName (unPackageName)
+import Ormolu.Config (DynOption (..))
+import Ormolu.Utils.Cabal
+import System.Directory
+import System.FilePath
+import System.IO.Temp (withSystemTempDirectory)
+import Test.Hspec
+
+spec :: Spec
+spec = do
+  describe "findCabalFile" $ do
+    let findsOrmoluCabal start expectedCabalFile = do
+          Just absolute <- findCabalFile start
+          absolute `shouldSatisfy` isAbsolute
+          makeRelativeToCurrentDirectory absolute `shouldReturn` expectedCabalFile
+    it "returns correct absolute path" $
+      findsOrmoluCabal "src/Ormolu/Config.hs" "ormolu.cabal"
+    it "finds correct path even when it starts from nonsense" $
+      findsOrmoluCabal "a/b/c/d/e" "ormolu.cabal"
+    it "returns Nothing when it cannot find a cabal file" $
+      findCabalFile "/foo.hs" `shouldReturn` Nothing
+    it "does not consider directories as .cabal files" $
+      withSystemTempDirectory "" $
+        \dir -> do
+          createDirectory $ dir </> ".cabal"
+          cabalFile <- findCabalFile (dir </> "foo/bar.hs")
+          cabalFile `shouldBe` Nothing
+  describe "parseCabalInfo" $ do
+    it "extracts correct cabal info from ormolu.cabal for src/Ormolu/Config.hs" $ do
+      (mentioned, CabalInfo {..}) <- parseCabalInfo "ormolu.cabal" "src/Ormolu/Config.hs"
+      mentioned `shouldBe` True
+      unPackageName ciPackageName `shouldBe` "ormolu"
+      ciDynOpts `shouldBe` [DynOption "-XGHC2021"]
+      Set.map unPackageName ciDependencies `shouldBe` Set.fromList ["Cabal-syntax", "Diff", "MemoTrie", "ansi-terminal", "array", "base", "binary", "bytestring", "choice", "containers", "directory", "file-embed", "filepath", "ghc-lib-parser", "megaparsec", "mtl", "syb", "text"]
+      ciCabalFilePath `shouldSatisfy` isAbsolute
+      makeRelativeToCurrentDirectory ciCabalFilePath `shouldReturn` "ormolu.cabal"
+    it "extracts correct cabal info from ormolu.cabal for tests/Ormolu/PrinterSpec.hs" $ do
+      (mentioned, CabalInfo {..}) <- parseCabalInfo "ormolu.cabal" "tests/Ormolu/PrinterSpec.hs"
+      mentioned `shouldBe` True
+      unPackageName ciPackageName `shouldBe` "ormolu"
+      ciDynOpts `shouldBe` [DynOption "-XGHC2021"]
+      Set.map unPackageName ciDependencies `shouldBe` Set.fromList ["Cabal-syntax", "QuickCheck", "base", "choice", "containers", "directory", "filepath", "ghc-lib-parser", "hspec", "hspec-megaparsec", "megaparsec", "ormolu", "path", "path-io", "temporary", "text"]
+      ciCabalFilePath `shouldSatisfy` isAbsolute
+      makeRelativeToCurrentDirectory ciCabalFilePath `shouldReturn` "ormolu.cabal"
+    it "handles correctly files that are not mentioned in ormolu.cabal" $ do
+      (mentioned, CabalInfo {..}) <- parseCabalInfo "ormolu.cabal" "src/FooBob.hs"
+      mentioned `shouldBe` False
+      unPackageName ciPackageName `shouldBe` "ormolu"
+      ciDynOpts `shouldBe` []
+      Set.map unPackageName ciDependencies `shouldBe` Set.fromList ["base"]
+      ciCabalFilePath `shouldSatisfy` isAbsolute
+      makeRelativeToCurrentDirectory ciCabalFilePath `shouldReturn` "ormolu.cabal"
+    it "handles `hs-source-dirs: .`" $ do
+      (_, CabalInfo {..}) <- parseTestCabalInfo "Foo.hs"
+      ciDynOpts `shouldContain` [DynOption "-XImportQualifiedPost"]
+    it "handles empty hs-source-dirs" $ do
+      (_, CabalInfo {..}) <- parseTestCabalInfo "Bar.hs"
+      ciDynOpts `shouldContain` [DynOption "-XImportQualifiedPost"]
+  where
+    parseTestCabalInfo f =
+      parseCabalInfo "data/cabal-tests/test.cabal" ("data/cabal-tests" </> f)
diff --git a/tests/Ormolu/Diff/TextSpec.hs b/tests/Ormolu/Diff/TextSpec.hs
--- a/tests/Ormolu/Diff/TextSpec.hs
+++ b/tests/Ormolu/Diff/TextSpec.hs
@@ -2,14 +2,11 @@
 
 module Ormolu.Diff.TextSpec (spec) where
 
-import Data.Text (Text)
-import qualified Data.Text.IO as T
+import Data.Text.IO.Utf8 qualified as T.Utf8
 import Ormolu.Diff.Text
 import Ormolu.Terminal
 import Path
-import Path.IO
-import qualified System.FilePath as FP
-import System.IO (hClose)
+import System.FilePath qualified as FP
 import Test.Hspec
 
 spec :: Spec
@@ -24,8 +21,10 @@
     stdTest "two-hunks" "main-and-baz" "main-and-baz-v2"
     stdTest "trimming" "spaced" "spaced-v2"
     stdTest "trailing-blank-line" "no-trailing-blank-line" "with-trailing-blank-line"
+    stdTest "trimming-trailing-both-eof" "applicative-before" "applicative-after"
+    stdTest "trimming-trailing-both-out-of-margin" "longer" "longer-v2"
 
--- | Test diff printig.
+-- | Test diff printing.
 stdTest ::
   -- | Name of the test case
   String ->
@@ -37,25 +36,16 @@
 stdTest name pathA pathB = it name $ do
   inputA <-
     parseRelFile (FP.addExtension pathA "hs")
-      >>= T.readFile . toFilePath . (diffInputsDir </>)
+      >>= T.Utf8.readFile . toFilePath . (diffInputsDir </>)
   inputB <-
     parseRelFile (FP.addExtension pathB "hs")
-      >>= T.readFile . toFilePath . (diffInputsDir </>)
+      >>= T.Utf8.readFile . toFilePath . (diffInputsDir </>)
   let expectedDiffPath = FP.addExtension name "txt"
   expectedDiffText <-
     parseRelFile expectedDiffPath
-      >>= T.readFile . toFilePath . (diffOutputsDir </>)
-  let Just actualDiff = diffText inputA inputB "TEST"
-  actualDiffText <- printDiff actualDiff
-  actualDiffText `shouldBe` expectedDiffText
-
--- | Print to a 'Text' value.
-printDiff :: TextDiff -> IO Text
-printDiff diff =
-  withSystemTempFile "ormolu-diff-test" $ \path h -> do
-    runTerm (printTextDiff diff) Never h
-    hClose h
-    T.readFile (toFilePath path)
+      >>= T.Utf8.readFile . toFilePath . (diffOutputsDir </>)
+  Just actualDiff <- pure $ diffText inputA inputB "TEST"
+  runTermPure (printTextDiff actualDiff) `shouldBe` expectedDiffText
 
 diffTestsDir :: Path Rel Dir
 diffTestsDir = $(mkRelDir "data/diff-tests")
diff --git a/tests/Ormolu/Fixity/ParserSpec.hs b/tests/Ormolu/Fixity/ParserSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/Fixity/ParserSpec.hs
@@ -0,0 +1,268 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Ormolu.Fixity.ParserSpec (spec) where
+
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Map.Strict qualified as Map
+import Data.Text (Text)
+import Data.Text qualified as T
+import Ormolu.Fixity
+import Ormolu.Fixity.Parser
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec.Error (ErrorFancy (..))
+
+spec :: Spec
+spec = do
+  describe "parseDotOrmolu" $ do
+    it "parses the empty input without choking" $
+      parseDotOrmolu "" ""
+        `shouldParse` (FixityOverrides Map.empty, ModuleReexports Map.empty)
+    it "parses a collection of fixity declarations" $
+      -- The example is taken from base.
+      parseDotOrmolu
+        ""
+        ( T.unlines
+            [ "infixr 9  .",
+              "infixr 5  ++",
+              "infixl 4  <$",
+              "infixl 1  >>, >>=",
+              "infixr 1  =<<",
+              "infixr 0  $, $!",
+              "infixl 4 <*>, <*, *>, <**>"
+            ]
+        )
+        `shouldParse` ( exampleFixityOverrides,
+                        ModuleReexports Map.empty
+                      )
+    it "accepts fractional operator precedences" $
+      parseDotOrmolu
+        ""
+        ( T.unlines
+            [ "infixr 3 >~<",
+              "infixr 3.3 |~|",
+              "infixr 3.7 <~>"
+            ]
+        )
+        `shouldParse` ( fractionalFixityOverrides,
+                        ModuleReexports Map.empty
+                      )
+    it "combines conflicting fixity declarations correctly" $
+      parseDotOrmolu
+        ""
+        ( T.unlines
+            [ "infixr 9 ., ^",
+              "infixr 7 ., $",
+              "infixr 9 ^ ",
+              "infixl 7 $"
+            ]
+        )
+        `shouldParse` ( FixityOverrides
+                          ( Map.fromList
+                              [ ("$", FixityInfo InfixL 7),
+                                (".", FixityInfo InfixR 7),
+                                ("^", FixityInfo InfixR 9)
+                              ]
+                          ),
+                        ModuleReexports Map.empty
+                      )
+    it "handles CRLF line endings correctly" $
+      parseDotOrmolu ""
+        `shouldSucceedOn` unlinesCrlf
+          [ "infixr 9  .",
+            "infixr 5  ++"
+          ]
+    it "parses inputs without a trailing newline" $
+      parseDotOrmolu ""
+        `shouldSucceedOn` T.intercalate
+          "\n"
+          [ "infixr 9  .",
+            "infixr 5  ++"
+          ]
+    it "fails with correct parse error (keyword wrong second line)" $
+      parseDotOrmolu "" "infixr 5 .\nfoobar 5 $"
+        `shouldFailWith` err
+          11
+          ( mconcat
+              [ utok 'f',
+                etoks "infix",
+                etoks "infixl",
+                etoks "infixr",
+                etoks "module",
+                eeof
+              ]
+          )
+    it "parses module re-exports and combines them correctly" $
+      parseDotOrmolu
+        ""
+        ( T.unlines
+            [ "module Control.Lens exports Control.Lens.Lens",
+              "module Control.Lens exports \"lens\" Control.Lens.At",
+              "module Text.Megaparsec exports Control.Monad.Combinators"
+            ]
+        )
+        `shouldParse` (FixityOverrides Map.empty, exampleModuleReexports)
+    it "parses fixity declarations + module re-export declarations with blanks" $
+      parseDotOrmolu
+        ""
+        ( T.unlines
+            [ "module Control.Lens exports Control.Lens.Lens",
+              "",
+              "infixr 5  ++",
+              "infixl 4  <$",
+              "",
+              "",
+              "module Control.Lens exports \"lens\" Control.Lens.At",
+              "infixr 9  .",
+              "module Text.Megaparsec exports Control.Monad.Combinators",
+              "infixl 1  >>, >>=",
+              "infixr 1  =<<",
+              "",
+              "infixr 0  $, $!",
+              "infixl 4 <*>, <*, *>, <**>"
+            ]
+        )
+        `shouldParse` (exampleFixityOverrides, exampleModuleReexports)
+  describe "parseFixtiyDeclaration" $ do
+    it "parses a simple infixr declaration" $
+      parseFixityDeclaration "infixr 5 $"
+        `shouldParse` [("$", FixityInfo InfixR 5)]
+    it "parses a simple infixl declaration" $
+      parseFixityDeclaration "infixl 5 $"
+        `shouldParse` [("$", FixityInfo InfixL 5)]
+    it "parses a simple infix declaration" $
+      parseFixityDeclaration "infix 5 $"
+        `shouldParse` [("$", FixityInfo InfixN 5)]
+    it "parses a declaration for a ticked identifier" $
+      parseFixityDeclaration "infixl 5 `foo`"
+        `shouldParse` [("foo", FixityInfo InfixL 5)]
+    it "parses a declaration for a ticked identifier (constructor case)" $
+      parseFixityDeclaration "infixl 5 `Foo`"
+        `shouldParse` [("Foo", FixityInfo InfixL 5)]
+    it "parses a multi-operator declaration" $
+      parseFixityDeclaration "infixl 5 $, ., `Foo`, `bar`"
+        `shouldParse` [ ("$", FixityInfo InfixL 5),
+                        (".", FixityInfo InfixL 5),
+                        ("Foo", FixityInfo InfixL 5),
+                        ("bar", FixityInfo InfixL 5)
+                      ]
+    it "parses a declaration with a unicode operator" $
+      parseFixityDeclaration "infixr 5 ×"
+        `shouldParse` [("×", FixityInfo InfixR 5)]
+    it "fails with correct parse error (keyword wrong)" $
+      parseFixityDeclaration "foobar 5 $"
+        `shouldFailWith` err
+          0
+          ( mconcat
+              [ utoks "foobar",
+                etoks "infix",
+                etoks "infixl",
+                etoks "infixr"
+              ]
+          )
+    it "fails with correct parse error (missing operator)" $
+      parseFixityDeclaration "infixr 5 "
+        `shouldFailWith` err
+          9
+          ( mconcat
+              [ ueof,
+                etok '`',
+                elabel "operator character"
+              ]
+          )
+    it "fails with correct parse error (trailing comma)" $
+      parseFixityDeclaration "infixr 5 ., "
+        `shouldFailWith` err
+          12
+          ( mconcat
+              [ ueof,
+                etok '`',
+                elabel "operator character"
+              ]
+          )
+    it "fails with correct parse error (precedence greater than 9)" $
+      parseFixityDeclaration "infixl 10 $"
+        `shouldFailWith` errFancy
+          7
+          (fancy (ErrorFail "precedence should not be greater than 9"))
+  describe "parseModuleReexportDeclaration" $ do
+    it "parses a re-export declaration" $
+      parseModuleReexportDeclaration "module Control.Lens exports Control.Lens.Lens"
+        `shouldParse` ( "Control.Lens",
+                        (Nothing, "Control.Lens.Lens") :| []
+                      )
+    it "parses a re-export declaration (explicit package)" $
+      parseModuleReexportDeclaration "module Control.Lens exports \"lens\" Control.Lens.Lens"
+        `shouldParse` ( "Control.Lens",
+                        (Just "lens", "Control.Lens.Lens") :| []
+                      )
+    it "fails with correct parse error (keyword wrong)" $
+      parseModuleReexportDeclaration "foo Control.Lens exports Control.Lens.Lens"
+        `shouldFailWith` err
+          0
+          ( mconcat
+              [ utoks "foo Co",
+                etoks "module"
+              ]
+          )
+    it "fails with correct parse error (module syntax)" $
+      parseModuleReexportDeclaration "module control.Lens exports Control.Lens.Lens"
+        `shouldFailWith` err
+          7
+          ( mconcat
+              [ utok 'c',
+                elabel "module name"
+              ]
+          )
+    it "fails with correct parse error (typo: export instead exports)" $
+      parseModuleReexportDeclaration "module Control.Lens export Control.Lens.Lens"
+        `shouldFailWith` err
+          20
+          ( mconcat
+              [ utoks "export ",
+                etoks "exports"
+              ]
+          )
+
+exampleFixityOverrides :: FixityOverrides
+exampleFixityOverrides =
+  FixityOverrides
+    ( Map.fromList
+        [ ("$", FixityInfo InfixR 0),
+          ("$!", FixityInfo InfixR 0),
+          ("*>", FixityInfo InfixL 4),
+          ("++", FixityInfo InfixR 5),
+          (".", FixityInfo InfixR 9),
+          ("<$", FixityInfo InfixL 4),
+          ("<*", FixityInfo InfixL 4),
+          ("<**>", FixityInfo InfixL 4),
+          ("<*>", FixityInfo InfixL 4),
+          ("=<<", FixityInfo InfixR 1),
+          (">>", FixityInfo InfixL 1),
+          (">>=", FixityInfo InfixL 1)
+        ]
+    )
+
+fractionalFixityOverrides :: FixityOverrides
+fractionalFixityOverrides =
+  FixityOverrides
+    ( Map.fromList
+        [ (">~<", FixityInfo InfixR 3),
+          ("|~|", FixityInfo InfixR 3.3),
+          ("<~>", FixityInfo InfixR 3.7)
+        ]
+    )
+
+exampleModuleReexports :: ModuleReexports
+exampleModuleReexports =
+  ModuleReexports . Map.fromList $
+    [ ( "Control.Lens",
+        (Nothing, "Control.Lens.Lens") :| [(Just "lens", "Control.Lens.At")]
+      ),
+      ( "Text.Megaparsec",
+        (Nothing, "Control.Monad.Combinators") :| []
+      )
+    ]
+
+unlinesCrlf :: [Text] -> Text
+unlinesCrlf = T.concat . fmap (<> "\r\n")
diff --git a/tests/Ormolu/Fixity/PrinterSpec.hs b/tests/Ormolu/Fixity/PrinterSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/Fixity/PrinterSpec.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE RecordWildCards #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Ormolu.Fixity.PrinterSpec (spec) where
+
+import Data.List (intercalate)
+import Data.List.NonEmpty qualified as NE
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as T
+import Distribution.ModuleName (ModuleName)
+import Distribution.ModuleName qualified as ModuleName
+import Distribution.Types.PackageName (PackageName, mkPackageName)
+import Ormolu.Fixity
+import Ormolu.Fixity.Parser
+import Ormolu.Fixity.Printer
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Test.QuickCheck
+
+instance Arbitrary FixityOverrides where
+  arbitrary =
+    FixityOverrides . Map.fromList
+      <$> listOf ((,) <$> genOperator <*> genFixityInfo)
+    where
+      genOperator =
+        OpName . T.pack <$> oneof [genNormalOperator, genIdentifier]
+      genNormalOperator =
+        listOf1 (scaleDown arbitrary `suchThat` isOperatorConstituent)
+      genIdentifier = do
+        x <- arbitrary `suchThat` isIdentifierFirstChar
+        xs <- listOf1 (scaleDown arbitrary `suchThat` isIdentifierConstituent)
+        return (x : xs)
+      genFixityInfo = do
+        fiDirection <-
+          elements
+            [ InfixL,
+              InfixR,
+              InfixN
+            ]
+        precedenceWholePart <- fromIntegral <$> chooseInt (0, 9)
+        precedenceFractionalPart <-
+          if precedenceWholePart < 9.0
+            then (* 0.1) . fromIntegral <$> chooseInt (0, 1)
+            else return 0
+        let fiPrecedence = precedenceWholePart + precedenceFractionalPart
+        return FixityInfo {..}
+
+instance Arbitrary ModuleReexports where
+  arbitrary = ModuleReexports . Map.fromListWith combine <$> listOf genReexport
+    where
+      combine x y = NE.sort (x <> y)
+      genReexport = do
+        exportingModule <- arbitrary
+        exports <- NE.sort . NE.fromList . getNonEmpty <$> scaleDown arbitrary
+        return (exportingModule, exports)
+
+instance Arbitrary PackageName where
+  arbitrary =
+    mkPackageName
+      <$> listOf1 (scaleDown arbitrary `suchThat` isPackageNameConstituent)
+
+instance Arbitrary ModuleName where
+  arbitrary =
+    ModuleName.fromString . intercalate "." <$> scaleDown (listOf1 genSegment)
+    where
+      genSegment = do
+        x <- arbitrary `suchThat` isModuleSegmentFirstChar
+        xs <- listOf (arbitrary `suchThat` isModuleSegmentConstituent)
+        return (x : xs)
+
+scaleDown :: Gen a -> Gen a
+scaleDown = scale (`div` 4)
+
+spec :: Spec
+spec = do
+  describe "parseFixityOverrides & printFixityOverrides" $
+    it "arbitrary fixity maps are printed and parsed back correctly" $
+      property $ \fixityOverrides moduleReexports ->
+        parseDotOrmolu "" (printDotOrmolu fixityOverrides moduleReexports)
+          `shouldParse` (fixityOverrides, moduleReexports)
diff --git a/tests/Ormolu/FixitySpec.hs b/tests/Ormolu/FixitySpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/FixitySpec.hs
@@ -0,0 +1,330 @@
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Ormolu.FixitySpec (spec) where
+
+import Data.Choice (pattern Without)
+import Data.Function ((&))
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Map.Strict qualified as Map
+import Data.Set qualified as Set
+import Data.Text qualified as T
+import Distribution.ModuleName (ModuleName)
+import Distribution.Types.PackageName (PackageName)
+import GHC.Types.Name (OccName)
+import GHC.Types.Name.Occurrence (mkVarOcc)
+import GHC.Types.Name.Reader
+import Language.Haskell.Syntax.ImpExp (ImportListInterpretation (..))
+import Language.Haskell.Syntax.Module.Name (mkModuleName)
+import Ormolu.Fixity
+import Ormolu.Fixity.Imports
+import Ormolu.Fixity.Internal
+import Ormolu.Utils (showOutputable)
+import Test.Hspec
+
+instance Show RdrName where
+  show = showOutputable
+
+spec :: Spec
+spec = do
+  it "gives the correct fixity info for (:) (built-in)" $
+    checkFixities
+      []
+      []
+      [(unqual ":", FixityApproximation (Just InfixR) 5 5)]
+  it "does not know operators from base if base is not a dependency" $
+    checkFixities
+      []
+      []
+      [ (unqual "$", defaultFixityApproximation),
+        (unqual "+", defaultFixityApproximation),
+        (unqual "++", defaultFixityApproximation)
+      ]
+  it "does not know operators from base if Prelude is not imported" $
+    checkFixities
+      []
+      []
+      [ (unqual "$", defaultFixityApproximation),
+        (unqual "+", defaultFixityApproximation),
+        (unqual "++", defaultFixityApproximation)
+      ]
+  it "infers fixities of operators from base correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Prelude"]
+      [ (unqual "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", FixityApproximation (Just InfixL) 6 6),
+        (unqual "++", FixityApproximation (Just InfixR) 5 5)
+      ]
+  it "does not know (>>>) when Control.Category is not imported" $
+    checkFixities
+      ["base"]
+      [import_ "Prelude"]
+      [ (unqual ">>>", defaultFixityApproximation)
+      ]
+  it "infer correct fixity for (>>>) when Control.Category is imported" $
+    checkFixities
+      ["base"]
+      [ import_ "Prelude",
+        import_ "Control.Category"
+      ]
+      [ (unqual ">>>", FixityApproximation (Just InfixR) 1 1)
+      ]
+  it "handles 'as' imports correctly" $
+    checkFixities
+      ["base"]
+      [ import_ "Control.Category" & as_ "Foo"
+      ]
+      [ (unqual ">>>", FixityApproximation (Just InfixR) 1 1),
+        (qual "Foo" ">>>", FixityApproximation (Just InfixR) 1 1),
+        (qual "Bar" ">>>", defaultFixityApproximation)
+      ]
+  it "handles 'qualified' imports correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Control.Category" & qualified_]
+      [ (unqual ">>>", defaultFixityApproximation),
+        (qual "Control.Category" ">>>", FixityApproximation (Just InfixR) 1 1)
+      ]
+  it "handles 'qualified as' imports correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Control.Category" & qualified_ & as_ "Foo"]
+      [ (unqual ">>>", defaultFixityApproximation),
+        (qual "Control.Category" ">>>", defaultFixityApproximation),
+        (qual "Foo" ">>>", FixityApproximation (Just InfixR) 1 1)
+      ]
+  it "handles explicit import lists correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Prelude" & exactly_ ["$"]]
+      [ (unqual "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", defaultFixityApproximation)
+      ]
+  it "handles hiding import lists correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Prelude" & hiding_ ["$"]]
+      [ (unqual "$", defaultFixityApproximation),
+        (unqual "+", FixityApproximation (Just InfixL) 6 6),
+        (unqual "++", FixityApproximation (Just InfixR) 5 5)
+      ]
+  it "handles qualified imports with explicit import lists correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Prelude" & qualified_ & exactly_ ["$"]]
+      [ (unqual "$", defaultFixityApproximation),
+        (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", defaultFixityApproximation),
+        (qual "Prelude" "+", defaultFixityApproximation)
+      ]
+  it "handles qualified import with hiding correctly" $
+    checkFixities
+      ["base"]
+      [import_ "Prelude" & qualified_ & hiding_ ["$"]]
+      [ (unqual "$", defaultFixityApproximation),
+        (qual "Prelude" "$", defaultFixityApproximation),
+        (unqual "+", defaultFixityApproximation),
+        (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)
+      ]
+  it "handles qualified import and explicit import lists correctly (1)" $
+    checkFixities
+      ["base"]
+      [ import_ "Prelude" & qualified_,
+        import_ "Prelude" & exactly_ ["$"]
+      ]
+      [ (unqual "$", FixityApproximation (Just InfixR) 0 0),
+        (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", defaultFixityApproximation),
+        (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)
+      ]
+  it "handles qualified import and explicit import lists correctly (2)" $
+    checkFixities
+      ["base"]
+      [ import_ "Prelude" & exactly_ ["$"],
+        import_ "Prelude" & qualified_
+      ]
+      [ (unqual "$", FixityApproximation (Just InfixR) 0 0),
+        (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", defaultFixityApproximation),
+        (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)
+      ]
+  it "handles qualified import and hiding import correctly (1)" $
+    checkFixities
+      ["base"]
+      [ import_ "Prelude" & qualified_,
+        import_ "Prelude" & hiding_ ["$"]
+      ]
+      [ (unqual "$", defaultFixityApproximation),
+        (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", FixityApproximation (Just InfixL) 6 6),
+        (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)
+      ]
+  it "handles qualified import and hiding import correctly (2)" $
+    checkFixities
+      ["base"]
+      [ import_ "Prelude" & hiding_ ["$"],
+        import_ "Prelude" & qualified_
+      ]
+      [ (unqual "$", defaultFixityApproximation),
+        (qual "Prelude" "$", FixityApproximation (Just InfixR) 0 0),
+        (unqual "+", FixityApproximation (Just InfixL) 6 6),
+        (qual "Prelude" "+", FixityApproximation (Just InfixL) 6 6)
+      ]
+  it "works for several imports from different packages" $
+    checkFixities
+      ["base", "esqueleto"]
+      [ import_ "Prelude",
+        import_ "Database.Esqueleto.Experimental" & qualified_ & as_ "E"
+      ]
+      [ (unqual "$", FixityApproximation (Just InfixR) 0 0),
+        (qual "E" "++.", FixityApproximation (Just InfixR) 5 5),
+        (qual "E" "on", FixityApproximation (Just InfixN) 9 9)
+      ]
+  it "merges approximations in case of a conflict" $
+    checkFixities
+      ["fclabels", "persistent"]
+      [ import_ "Data.Label.Monadic",
+        import_ "Database.Persist"
+      ]
+      [ (unqual "=.", FixityApproximation (Just InfixR) 2 3)
+      ]
+  it "correctly handles package-qualified imports (1)" $
+    checkFixities
+      ["esqueleto"]
+      [package_ "esqueleto" $ import_ "Database.Esqueleto.Experimental"]
+      [(unqual "++.", FixityApproximation (Just InfixR) 5 5)]
+  it "correctly handles package-qualified imports (2)" $
+    checkFixities
+      ["esqueleto"]
+      [package_ "bob" $ import_ "Database.Esqueleto.Experimental"]
+      [(unqual "++.", defaultFixityApproximation)]
+  it "default module re-exports: Control.Lens brings into scope Control.Lens.Lens" $
+    checkFixities
+      ["lens"]
+      ( applyModuleReexports
+          defaultModuleReexports
+          [import_ "Control.Lens"]
+      )
+      [(unqual "<+~", FixityApproximation (Just InfixR) 4 4)]
+  it "default module re-exports: Control.Lens qualified brings into scope Control.Lens.Lens" $
+    checkFixities
+      ["lens"]
+      ( applyModuleReexports
+          defaultModuleReexports
+          [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" $
+    checkFixities
+      ["lens"]
+      ( applyModuleReexports
+          defaultModuleReexports
+          [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
+              "Foo"
+              ((Nothing, "Control.Lens") :| [])
+              (unModuleReexports defaultModuleReexports)
+    checkFixities
+      ["lens"]
+      ( applyModuleReexports
+          reexports
+          [import_ "Foo"]
+      )
+      [ (unqual "<+~", FixityApproximation (Just InfixR) 4 4)
+      ]
+
+-- | Build a fixity map using the Hoogle database and then check the fixity
+-- of the specified subset of operators.
+checkFixities ::
+  -- | List of dependencies
+  [PackageName] ->
+  -- | Imports
+  [FixityImport] ->
+  -- | Associative list representing a subset of the resulting fixity map
+  -- that should be checked.
+  [(RdrName, FixityApproximation)] ->
+  Expectation
+checkFixities dependencies fixityImports expectedResult =
+  actualResult `shouldBe` expectedResult
+  where
+    actualResult =
+      fmap
+        (\(k, _) -> (k, inferFixity (Without #debug) k resultMap))
+        expectedResult
+    resultMap =
+      moduleFixityMap
+        (packageFixityMap (Set.fromList dependencies))
+        fixityImports
+
+qual :: String -> OpName -> RdrName
+qual moduleName opName = mkRdrQual (mkModuleName moduleName) (opNameToOccName opName)
+
+unqual :: OpName -> RdrName
+unqual = mkRdrUnqual . opNameToOccName
+
+opNameToOccName :: OpName -> OccName
+opNameToOccName = mkVarOcc . T.unpack . unOpName
+
+-- | Explicitly specify the package.
+package_ :: PackageName -> FixityImport -> FixityImport
+package_ packageName fixityImport =
+  fixityImport
+    { fimportPackage = Just packageName
+    }
+
+-- | Construct a simple 'FixityImport'.
+import_ :: ModuleName -> FixityImport
+import_ moduleName =
+  FixityImport
+    { fimportPackage = Nothing,
+      fimportModule = moduleName,
+      fimportQualified = UnqualifiedAndQualified moduleName,
+      fimportList = Nothing
+    }
+
+-- | Adds an alias for an import.
+as_ :: ModuleName -> FixityImport -> FixityImport
+as_ moduleName fixityImport =
+  fixityImport
+    { fimportQualified = case fimportQualified fixityImport of
+        UnqualifiedAndQualified _ -> UnqualifiedAndQualified moduleName
+        OnlyQualified _ -> OnlyQualified moduleName
+    }
+
+-- | Qualified imports.
+qualified_ :: FixityImport -> FixityImport
+qualified_ fixityImport =
+  fixityImport
+    { fimportQualified = case fimportQualified fixityImport of
+        UnqualifiedAndQualified m -> OnlyQualified m
+        OnlyQualified m -> OnlyQualified m
+    }
+
+-- | Exact import lists.
+exactly_ :: [OpName] -> FixityImport -> FixityImport
+exactly_ opNames fixityImports =
+  fixityImports
+    { fimportList = Just (Exactly, opNames)
+    }
+
+-- | Hiding.
+hiding_ :: [OpName] -> FixityImport -> FixityImport
+hiding_ opNames fixityImports =
+  fixityImports
+    { fimportList = Just (EverythingBut, opNames)
+    }
diff --git a/tests/Ormolu/OpTreeSpec.hs b/tests/Ormolu/OpTreeSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/OpTreeSpec.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
+
+module Ormolu.OpTreeSpec (spec) where
+
+import Data.Choice (pattern Without)
+import Data.Map.Strict qualified as Map
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Types.Name (mkOccName, varName)
+import GHC.Types.Name.Reader (mkRdrUnqual)
+import Ormolu.Fixity
+import Ormolu.Fixity.Internal
+import Ormolu.Printer.Operators
+import Test.Hspec
+
+n :: Text -> OpTree Text OpName
+n = OpNode
+
+-- | Check that the input tree is actually reassociated as expected.
+checkReassociate ::
+  -- | Fixity map used for the reassociation
+  [(OpName, FixityInfo)] ->
+  -- | Input tree
+  OpTree Text OpName ->
+  -- | Expected output tree
+  OpTree Text OpName ->
+  Expectation
+checkReassociate fixities inputTree expectedOutputTree =
+  removeOpInfo actualOutputTree `shouldBe` expectedOutputTree
+  where
+    removeOpInfo (OpNode x) = OpNode x
+    removeOpInfo (OpBranches exprs ops) =
+      OpBranches (removeOpInfo <$> exprs) (opiOp <$> ops)
+    actualOutputTree = reassociateOpTree (Without #debug) convertName modFixityMap inputTree
+    modFixityMap = ModuleFixityMap (Map.map Given (Map.fromList fixities))
+    convertName = Just . mkRdrUnqual . mkOccName varName . T.unpack . unOpName
+
+spec :: Spec
+spec = do
+  it "flattens a tree correctly" $ do
+    let inputTree =
+          OpBranches
+            [ OpBranches
+                [OpBranches [n "a", n "b"] ["+"], n "c"]
+                ["+"],
+              n "d"
+            ]
+            ["+"]
+        outputTree =
+          OpBranches [n "a", n "b", n "c", n "d"] ["+", "+", "+"]
+        fixities = [("+", FixityInfo InfixL 5)]
+    checkReassociate fixities inputTree outputTree
+
+  it "uses 'minOps' strategy by default" $ do
+    let inputTree =
+          OpBranches
+            [n "a", n "b", n "c", n "d", n "e", n "f"]
+            ["*", "*", "+", "*", "-"]
+        outputTree =
+          OpBranches
+            [ OpBranches [n "a", n "b", n "c"] ["*", "*"],
+              OpBranches [n "d", n "e"] ["*"],
+              n "f"
+            ]
+            ["+", "-"]
+        fixities =
+          [ ("+", FixityInfo InfixL 5),
+            ("*", FixityInfo InfixL 7),
+            ("-", FixityInfo InfixL 5)
+          ]
+    checkReassociate fixities inputTree outputTree
+
+  it "uses 'maxOps' strategy if 'minOps' strategy fails" $ do
+    let inputTree =
+          OpBranches
+            [n "a", n "b", n "c", n "d", n "e", n "f"]
+            ["*", "*", "+", "*", "-"]
+        outputTree =
+          OpBranches
+            [ OpBranches [n "a", n "b", n "c"] ["*", "*"],
+              OpBranches [n "d", n "e"] ["*"],
+              n "f"
+            ]
+            ["+", "-"]
+        fixities =
+          [ ("+", FixityInfo InfixL 5),
+            ("*", FixityInfo InfixL 8),
+            ("-", FixityInfo InfixL 5)
+          ]
+    checkReassociate fixities inputTree outputTree
+
+  it
+    "defaults to 'hardSplitter' strategy if both 'minOps' and 'maxOps' \
+    \strategies fail"
+    $ do
+      let inputTree =
+            OpBranches
+              [n "a", n "b", n "c", n "d", n "e", n "f"]
+              ["@", "@", "|", "@", "$"]
+          outputTree =
+            OpBranches
+              [ OpBranches
+                  [n "a", n "b", n "c", n "d", n "e"]
+                  ["@", "@", "|", "@"],
+                n "f"
+              ]
+              ["$"]
+          fixities =
+            [ ("@", FixityInfo InfixL 4),
+              ("|", FixityInfo InfixL 4),
+              ("$", FixityInfo InfixR 0)
+            ]
+      checkReassociate fixities inputTree outputTree
+
+  it "reassociates correctly: complex example 1" $ do
+    let inputTree =
+          OpBranches
+            [n "f", n "1", n "2", n "3", n "4", n "5", n "6"]
+            ["$", "+", "*", "$", "*", "+"]
+        outputTree =
+          OpBranches
+            [ n "f",
+              OpBranches
+                [n "1", OpBranches [n "2", n "3"] ["*"]]
+                ["+"],
+              OpBranches
+                [OpBranches [n "4", n "5"] ["*"], n "6"]
+                ["+"]
+            ]
+            ["$", "$"]
+        fixities =
+          [ ("$", FixityInfo InfixR 0),
+            ("+", FixityInfo InfixL 6),
+            ("*", FixityInfo InfixL 7)
+          ]
+    checkReassociate fixities inputTree outputTree
diff --git a/tests/Ormolu/Parser/OptionsSpec.hs b/tests/Ormolu/Parser/OptionsSpec.hs
--- a/tests/Ormolu/Parser/OptionsSpec.hs
+++ b/tests/Ormolu/Parser/OptionsSpec.hs
@@ -1,8 +1,9 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Ormolu.Parser.OptionsSpec (spec) where
 
-import qualified Data.Text as T
+import Data.Text qualified as T
 import Ormolu
 import Test.Hspec
 
@@ -10,7 +11,7 @@
 spec = describe "GHC options in source files take priority" $ do
   it "default extensions can be disabled locally" $ do
     let src =
-          unlines
+          T.unlines
             [ "{-# LANGUAGE NoBlockArguments #-}",
               "",
               "test = test do test"
@@ -20,7 +21,7 @@
       _ -> False
   it "extensions disabled via CLI can be enabled locally" $ do
     let src =
-          unlines
+          T.unlines
             [ "{-# LANGUAGE BlockArguments #-}",
               "",
               "test = test do test"
@@ -29,4 +30,4 @@
   where
     fixedPoint opts input = do
       output <- ormolu defaultConfig {cfgDynOptions = DynOption <$> opts} "<input>" input
-      T.unpack output `shouldBe` input
+      output `shouldBe` input
diff --git a/tests/Ormolu/Parser/ParseFailureSpec.hs b/tests/Ormolu/Parser/ParseFailureSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/Parser/ParseFailureSpec.hs
@@ -0,0 +1,26 @@
+{-# LANGUAGE LambdaCase #-}
+
+module Ormolu.Parser.ParseFailureSpec (spec) where
+
+import Ormolu
+import Ormolu.Utils (showOutputable)
+import System.FilePath
+import Test.Hspec
+
+spec :: Spec
+spec = do
+  "disabling-preserves-error-location.hs" `failsAt` "12:1"
+  "line-pragma.hs" `failsAt` "4:47"
+
+failsAt :: String -> String -> Spec
+failsAt filename location =
+  let filePath = baseDir </> filename
+   in it (filename ++ " fails at " ++ location) $
+        ormoluFile defaultConfig filePath
+          `shouldThrow` \case
+            OrmoluParsingFailed srcSpan _ ->
+              showOutputable srcSpan == filePath ++ ":" ++ location
+            _ -> False
+
+baseDir :: FilePath
+baseDir = "data" </> "parse-failures"
diff --git a/tests/Ormolu/Parser/PragmaSpec.hs b/tests/Ormolu/Parser/PragmaSpec.hs
--- a/tests/Ormolu/Parser/PragmaSpec.hs
+++ b/tests/Ormolu/Parser/PragmaSpec.hs
@@ -1,5 +1,9 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 module Ormolu.Parser.PragmaSpec (spec) where
 
+import Data.Text (Text)
+import Data.Text qualified as T
 import Ormolu.Parser.Pragma
 import Test.Hspec
 
@@ -20,7 +24,7 @@
     stdTest "{-# OPTIONS_GHC foo bar baz  #-}" (Just $ PragmaOptionsGHC "foo bar baz")
     stdTest "{-#OPTIONS_HADDOCK foo, bar, baz  #-}" (Just $ PragmaOptionsHaddock "foo, bar, baz")
 
-stdTest :: String -> Maybe Pragma -> Spec
+stdTest :: Text -> Maybe Pragma -> Spec
 stdTest input result =
-  it input $
+  it (T.unpack input) $
     parsePragma input `shouldBe` result
diff --git a/tests/Ormolu/PrinterSpec.hs b/tests/Ormolu/PrinterSpec.hs
--- a/tests/Ormolu/PrinterSpec.hs
+++ b/tests/Ormolu/PrinterSpec.hs
@@ -1,18 +1,23 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TemplateHaskell #-}
 
 module Ormolu.PrinterSpec (spec) where
 
 import Control.Exception
 import Control.Monad
-import Control.Monad.IO.Class
 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 qualified Data.Text as T
-import qualified Data.Text.IO as T
+import Data.Text qualified as T
+import Data.Text.IO.Utf8 qualified as T.Utf8
 import Ormolu
+import Ormolu.Fixity
 import Path
 import Path.IO
-import qualified System.FilePath as F
+import System.Environment (lookupEnv)
+import System.FilePath qualified as F
 import Test.Hspec
 
 spec :: Spec
@@ -20,24 +25,52 @@
   es <- runIO locateExamples
   forM_ es checkExample
 
+-- | 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 :: Path Rel File -> Spec
 checkExample srcPath' = it (fromRelFile srcPath' ++ " works") . withNiceExceptions $ do
   let srcPath = examplesDir </> srcPath'
+      inputPath = fromRelFile srcPath
+      config =
+        defaultConfig
+          { cfgSourceType = detectSourceType inputPath,
+            cfgFixityOverrides = testsuiteOverrides,
+            cfgDependencies =
+              Set.fromList
+                [ "base",
+                  "esqueleto",
+                  "hspec",
+                  "lens",
+                  "servant"
+                ]
+          }
   expectedOutputPath <- deriveOutput srcPath
   -- 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
   -- 'ormoluFile' automatically.)
-  formatted0 <- ormoluFile defaultConfig (fromRelFile srcPath)
+  formatted0 <- ormoluFile config inputPath
   -- 3. Check the output against expected output. Thus all tests should
   -- include two files: input and expected output.
-  -- T.writeFile (fromRelFile expectedOutputPath) formatted0
-  expected <- (liftIO . T.readFile . fromRelFile) expectedOutputPath
+  whenShouldRegenerateOutput $
+    T.Utf8.writeFile (fromRelFile expectedOutputPath) formatted0
+  expected <- T.Utf8.readFile $ fromRelFile expectedOutputPath
   shouldMatch False formatted0 expected
   -- 4. Check that running the formatter on the output produces the same
   -- output again (the transformation is idempotent).
-  formatted1 <- ormolu defaultConfig "<formatted>" (T.unpack formatted0)
+  formatted1 <- ormolu config "<formatted>" formatted0
   shouldMatch True formatted1 formatted0
 
 -- | Build list of examples for testing.
@@ -51,13 +84,15 @@
 isInput path =
   let s = fromRelFile path
       (s', exts) = F.splitExtensions s
-   in exts == ".hs" && not ("-out" `isSuffixOf` s')
+   in exts `elem` [".hs", ".hsig"] && not ("-out" `isSuffixOf` s')
 
 -- | For given path of input file return expected name of output.
 deriveOutput :: Path Rel File -> IO (Path Rel File)
 deriveOutput path =
   parseRelFile $
-    F.addExtension (F.dropExtensions (fromRelFile path) ++ "-out") "hs"
+    F.addExtension (radical ++ "-out") exts
+  where
+    (radical, exts) = F.splitExtensions (fromRelFile path)
 
 -- | A version of 'shouldBe' that is specialized to comparing 'Text' values.
 -- It also prints multi-line snippets in a more readable form.
@@ -89,3 +124,8 @@
   where
     h :: OrmoluException -> IO ()
     h = expectationFailure . displayException
+
+whenShouldRegenerateOutput :: IO () -> IO ()
+whenShouldRegenerateOutput action = do
+  shouldRegenerateOutput <- isJust <$> lookupEnv "ORMOLU_REGENERATE_EXAMPLES"
+  when shouldRegenerateOutput action
