diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,162 @@
+## Fourmolu 0.4.0.0
+
+* Write "Loaded config from" message to `stderr` instead of `stdout` ([#42](https://github.com/fourmolu/fourmolu/pull/42))
+* Fixed issue with multiline haddocks in indented contexts ([#64](https://github.com/fourmolu/fourmolu/pull/64))
+* Avoid renaming magic comments ([#90](https://github.com/fourmolu/fourmolu/pull/90))
+
+### Upstream changes:
+
+#### 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
+  683](https://github.com/tweag/ormolu/issues/683).
+
+* `TemplateHaskell` is not enabled by default anymore. [Issue
+  699](https://github.com/tweag/ormolu/issues/699).
+
+* Made record dot pre-processor rendering idempotent in certain specific
+  cases. [Issue 701](https://github.com/tweag/ormolu/issues/701).
+
+* Added support for arrow command application. [Issue
+  716](https://github.com/tweag/ormolu/issues/716).
+
+* Switched to `ghc-lib-parser-9.0.1`. [PR
+  722](https://github.com/tweag/ormolu/pull/722).
+   * Support for the new language extensions:
+      * `LexicalNegation`, `LinearTypes`: disabled by default
+      * `QualifiedDo`: enabled by default
+   * Due to [upstream changes in whitespace sensitity](
+     https://gitlab.haskell.org/ghc/ghc/-/wikis/migration/9.0#whitespace-sensitive-and-),
+     `TypeApplications` is now *enabled* by default. [Issue
+     452](https://github.com/tweag/ormolu/issues/452).
+   * Haddocks on declarations in files without a `module` header are no longer
+     deleted. [Issue 480](https://github.com/tweag/ormolu/issues/480).
+   * Due to a change in Haddock parsing, empty Haddock comments on function
+     arguments now get deleted.
+
+* CTYPE pragmas are now preserved. [Issue 689](
+  https://github.com/tweag/ormolu/issues/689).
+
+* `TypeApplications` in data/type family instances are now supported. [Issue
+  698](https://github.com/tweag/ormolu/issues/698).
+
+* Formatting infix arrow command formations now preserves the AST. [Issue
+  718](https://github.com/tweag/ormolu/issues/718).
+
+* Surround code in brackets with spaces if it contains a `StarIsType` `*` to
+  prevent unparseable output. [Issue 704](https://github.com/tweag/ormolu/issues/704).
+
+* Formatting applied multiline constructs in do blocks now preserves the AST.
+  [Issue 707](https://github.com/tweag/ormolu/issues/707).
+
+  This will sometimes result in odd indentations, e.g. this snippet is a
+  fixed point:
+  ```haskell
+  foo = do
+    do
+      (+1)
+     1
+  ```
+
+* GHC options passed in via the CLI can now be overridden in local files.
+  Previously, if an extension was disabled via the CLI, it could not be
+  re-enabled per file.
+
+* `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).
+
+* Minus signs in literal patterns are now preserved in all cases. [Issue
+  733](https://github.com/tweag/ormolu/issues/733).
+
+* 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. [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).
+
+#### Ormolu 0.1.4.1
+
+* Added command line option `--color` to control how diffs are printed.
+  Standardized the way errors are printed.
+
+#### Ormolu 0.1.4.0
+
+* Added support for monad comprehensions. [Issue
+  665](https://github.com/tweag/ormolu/issues/665).
+
+* Fixed a bug when a space was inserted in front of promoted types even when
+  it wasn't strictly necessary. [Issue
+  668](https://github.com/tweag/ormolu/issues/668).
+
+* Now the checking mode displays diffs per file when unformatted files are
+  found. The rendering of the diffs is also improved. [Issue
+  656](https://github.com/tweag/ormolu/issues/656).
+
+#### Ormolu 0.1.3.1
+
+* Fixed a problem with multiline record updates using the record dot
+  preprocessor. [Issue 658](https://github.com/tweag/ormolu/issues/658).
+
 ## Fourmolu 0.3.0.0
 
 * New config option `newlines-between-decls`, to choose the number of blank lines between top-level declarations.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,6 +3,17 @@
 [![License BSD3](https://img.shields.io/badge/license-BSD3-brightgreen.svg)](http://opensource.org/licenses/BSD-3-Clause)
 [![Hackage](https://img.shields.io/hackage/v/fourmolu.svg?style=flat)](https://hackage.haskell.org/package/fourmolu)
 
+* [Configuration](#configuration)
+* [Building and installation](#building-and-installation)
+* [Usage](#usage)
+    * [Editor integration](#editor-integration)
+    * [Magic comments](#magic-comments)
+    * [Account for .cabal files](#account-for-cabal-files)
+    * [Exit codes](#exit-codes)
+* [Limitations](#limitations)
+* [Contributing](#contributing)
+* [License](#license)
+
 Fourmolu is a formatter for Haskell source code. It is a fork of [Ormolu](https://github.com/tweag/ormolu), with the intention to continue to merge upstream improvements.
 
 We share all bar one of Ormolu's goals:
@@ -11,18 +22,17 @@
   [`haskell-src-exts`](https://hackage.haskell.org/package/haskell-src-exts).
 * Let some whitespace be programmable. The layout of the input influences
   the layout choices in the output. This means that the choices between
-  single-line/multi-line layouts in each particular situation are made by
-  the user, not by an algorithm. This makes the implementation simpler and
-  leaves some control to the user while still guaranteeing that the
-  formatted code is stylistically consistent.
+  single-line/multi-line layouts in certain situations are made by the user,
+  not by an algorithm. This makes the implementation simpler and leaves some
+  control to the user while still guaranteeing that the formatted code is
+  stylistically consistent.
 * Writing code in such a way so it's easy to modify and maintain.
-* That formatting style aims to result in minimal diffs while still
-  remaining very close to “conventional” Haskell formatting people use.
+* That formatting style aims to result in minimal diffs.
 * Choose a style compatible with modern dialects of Haskell. As new Haskell
   extensions enter broad use, we may change the style to accommodate them.
 * Idempotence: formatting already formatted code doesn't change it.
-* Be well-tested and robust to the point that it can be used in large
-  projects without exposing unfortunate, disappointing bugs here and there.
+* Be well-tested and robust so that the formatter can be used in large
+  projects.
 * ~~Implementing one “true” formatting style which admits no configuration.~~ We allow configuration of various parameters, via CLI options or config files. We encourage any contributions which add further flexibility.
 
 ## Configuration
@@ -44,7 +54,7 @@
 
 These options can also be set on the command line (which takes precedence over config files). Run `fourmolu -h` to see all options.
 
-## Building
+## Building and installation
 
 Simply run `cabal v2-install fourmolu`, to install the latest release from Hackage.
 
@@ -70,9 +80,23 @@
 $ fourmolu -i $(find . -name '*.hs')
 ```
 
+Or find all files in a project with `git ls-files`:
+
+```console
+$ fourmolu --mode inplace $(git ls-files '*.hs')
+```
+
+To check if files are are already formatted (useful on CI):
+
+```console
+$ fourmolu --mode check $(find . -name '*.hs')
+```
+
+### Editor integration
+
 Fourmolu can be integrated with your editor via the [Haskell Language Server](https://github.com/haskell/haskell-language-server).
 
-## Magic comments
+### Magic comments
 
 Fourmolu understands two magic comments:
 
@@ -89,19 +113,50 @@
 This allows us to disable formatting selectively for code between these
 markers or disable it for the entire file. To achieve the latter, just put
 `{- FOURMOLU_DISABLE -}` at the very top. Note that for Fourmolu to work the
-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.
 
-`{- ORMOLU_DISABLE -}` and `{- ORMOLU_ENABLE -}`, respectively, can be used to the same effect.
+`{- ORMOLU_DISABLE -}` and `{- ORMOLU_ENABLE -}`, respectively, can be used to the same effect,
+and the two styles of magic comments can be mixed.
 
-## Current limitations
+### Account for .cabal files
 
+Many cabal and stack projects use `default-extensions` to enable GHC
+language extensions in all source files. With the
+`--cabal-default-extensions` flag, Ormolu will take them into consideration
+during formatting.
+
+When you format input from stdin, you can pass `--stdin-input-file` which
+will give Ormolu the location of the Haskell source file that should be used
+as the starting point for searching for a suitable .cabal file.
+
+### Exit codes
+
+Exit code | Meaning
+----------|-----------------------------------------------
+0         | Success
+1         | General problem
+2         | CPP used (deprecated)
+3         | Parsing of original input failed
+4         | Parsing of formatted code failed
+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
+100       | In checking mode: unformatted files
+101       | Inplace and check modes do not work with stdin
+102       | Other issue (with multiple input files)
+400       | Failed to load Fourmolu configuration file
+
+## Limitations
+
 * CPP support is experimental. CPP is virtually impossible to handle
   correctly, so we process them as a sort of unchangeable snippets. This
   works only in simple cases when CPP conditionals surround top-level
-  declarations. See the [CPP](https://github.com/tweag/ormolu/blob/master/DESIGN.md#cpp) section in the design notes for
-  a discussion of the dangers.
+  declarations. See the [CPP](https://github.com/tweag/ormolu/blob/master/DESIGN.md#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.
 * Various minor idempotence issues, most of them are related to comments.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -10,98 +10,134 @@
 
 module Main (main) where
 
-import Control.Exception (SomeException, displayException, try)
+import Control.Exception (throwIO)
 import Control.Monad
 import Data.Bool (bool)
-import Data.Either (lefts)
 import Data.Functor.Identity (Identity (..))
 import Data.List (intercalate, sort)
-import Data.Maybe (fromMaybe)
+import Data.Maybe (fromMaybe, mapMaybe)
 import qualified Data.Text.IO as TIO
 import Data.Version (showVersion)
 import Development.GitRev
 import Options.Applicative
 import Ormolu
 import Ormolu.Config
+import Ormolu.Diff.Text (diffText, printTextDiff)
 import Ormolu.Parser (manualExts)
+import Ormolu.Terminal
 import Ormolu.Utils (showOutputable)
+import Ormolu.Utils.Extensions (getCabalExtensionDynOptions)
+import Ormolu.Utils.IO
 import Paths_fourmolu (version)
 import System.Directory (getCurrentDirectory)
 import System.Exit (ExitCode (..), exitWith)
+import qualified System.FilePath as FP
 import System.IO (hPutStrLn, stderr)
 
 -- | Entry point of the program.
 main :: IO ()
-main = withPrettyOrmoluExceptions $ do
+main = do
   opts@Opts {..} <- execParser optsParserInfo
-  let formatStdIn = do
-        cur <- getCurrentDirectory
-        cfg <- mkConfig cur opts
-        formatOne optMode cfg Nothing
-  case optInputFiles of
-    [] -> formatStdIn
-    ["-"] -> formatStdIn
-    [x] -> flip (formatOne optMode) (Just x) =<< mkConfig x opts
-    xs@(x : _) -> do
-      cfg <- mkConfig x opts
-      -- It is possible to get IOException, error's and 'OrmoluException's
-      -- from 'formatOne', so we just catch everything.
-      errs <-
-        lefts
-          <$> mapM
-            (try @SomeException . formatOne optMode cfg . Just)
-            (sort xs)
-      unless (null errs) $ do
-        mapM_ (hPutStrLn stderr . displayException) errs
-        exitWith (ExitFailure 102)
 
+  cfg <- case optInputFiles of
+    [] -> mkConfigFromCWD opts
+    ["-"] -> mkConfigFromCWD opts
+    file : _ -> mkConfig file opts
+  let formatOne' = formatOne optCabalDefaultExtensions optMode cfg
+
+  exitCode <- case optInputFiles of
+    [] -> formatOne' Nothing
+    ["-"] -> formatOne' Nothing
+    [x] -> formatOne' (Just x)
+    xs -> do
+      let selectFailure = \case
+            ExitSuccess -> Nothing
+            ExitFailure n -> Just n
+      errorCodes <-
+        mapMaybe selectFailure <$> mapM (formatOne' . Just) (sort xs)
+      return $
+        if null errorCodes
+          then ExitSuccess
+          else
+            ExitFailure $
+              if all (== 100) errorCodes
+                then 100
+                else 102
+  exitWith exitCode
+
 -- | Format a single input.
 formatOne ::
+  -- | Whether to respect default-extensions from .cabal files
+  CabalDefaultExtensionsOpts ->
   -- | Mode of operation
   Mode ->
   -- | Configuration
   Config RegionIndices ->
   -- | File to format or stdin as 'Nothing'
   Maybe FilePath ->
-  IO ()
-formatOne mode config = \case
-  Nothing -> do
-    r <- ormoluStdin config
-    case mode of
-      Stdout -> TIO.putStr r
-      _ -> 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.
-        exitWith (ExitFailure 101)
-  Just inputFile -> do
-    originalInput <- TIO.readFile inputFile
-    formattedInput <- ormoluFile config inputFile
-    case mode of
-      Stdout ->
-        TIO.putStr formattedInput
-      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
-      Check -> do
-        when (formattedInput /= originalInput) $
-          -- 100 is different to all the other exit code that are emitted
-          -- either from an 'OrmoluException' or from 'error' and
-          -- 'notImplemented'.
-          exitWith (ExitFailure 100)
+  IO ExitCode
+formatOne CabalDefaultExtensionsOpts {..} mode config mpath =
+  withPrettyOrmoluExceptions (cfgColorMode config) $
+    case FP.normalise <$> mpath of
+      Nothing -> do
+        extraDynOptions <-
+          if optUseCabalDefaultExtensions
+            then case optStdinInputFile of
+              Just stdinInputFile ->
+                getCabalExtensionDynOptions stdinInputFile
+              Nothing -> throwIO OrmoluMissingStdinInputFile
+            else pure []
+        r <- ormoluStdin (configPlus extraDynOptions)
+        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
+        extraDynOptions <-
+          if optUseCabalDefaultExtensions
+            then getCabalExtensionDynOptions inputFile
+            else pure []
+        originalInput <- readFileUtf8 inputFile
+        formattedInput <- ormoluFile (configPlus extraDynOptions) 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) $
+              writeFileUtf8 inputFile formattedInput
+            return ExitSuccess
+          Check ->
+            case diffText originalInput formattedInput inputFile of
+              Nothing -> return ExitSuccess
+              Just diff -> do
+                runTerm (printTextDiff diff) (cfgColorMode config) stderr
+                -- 100 is different to all the other exit code that are emitted
+                -- either from an 'OrmoluException' or from 'error' and
+                -- 'notImplemented'.
+                return (ExitFailure 100)
+  where
+    configPlus dynOpts = config {cfgDynOptions = cfgDynOptions config ++ dynOpts}
 
 ----------------------------------------------------------------------------
--- Command line options parsing.
+-- Command line options parsing
 
 data Opts = Opts
   { -- | Mode of operation
     optMode :: !Mode,
     -- | Ormolu 'Config'
     optConfig :: !(Config RegionIndices),
+    -- | Options for respecting default-extensions from .cabal files
+    optCabalDefaultExtensions :: CabalDefaultExtensionsOpts,
     -- | Fourmolu-specific options
     optPrinterOpts :: !PrinterOptsPartial,
     -- | Haskell source files to format or stdin (when the list is empty)
@@ -119,13 +155,21 @@
     Check
   deriving (Eq, Show, Bounded, Enum)
 
+-- | Configuration for how to account for default-extension
+-- from .cabal files
+data CabalDefaultExtensionsOpts = CabalDefaultExtensionsOpts
+  { -- | Account for default-extensions from .cabal files
+    optUseCabalDefaultExtensions :: 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 $
-    [ fullDesc,
-      progDesc "",
-      header ""
-    ]
+    [fullDesc]
   where
     ver :: Parser (a -> a)
     ver =
@@ -165,16 +209,30 @@
                 short 'm',
                 metavar "MODE",
                 value Stdout,
-                help "Mode of operation: 'stdout' (default), 'inplace', or 'check'"
+                help "Mode of operation: 'stdout' (the default), 'inplace', or 'check'"
               ]
         )
     <*> configParser
+    <*> cabalDefaultExtensionsParser
     <*> printerOptsParser
     <*> (many . strArgument . mconcat)
       [ metavar "FILE",
-        help "Haskell source files to format or stdin (default)"
+        help "Haskell source files to format or stdin (the default)"
       ]
 
+cabalDefaultExtensionsParser :: Parser CabalDefaultExtensionsOpts
+cabalDefaultExtensionsParser =
+  CabalDefaultExtensionsOpts
+    <$> (switch . mconcat)
+      [ short 'e',
+        long "cabal-default-extensions",
+        help "Account for default-extensions from .cabal 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
@@ -199,6 +257,12 @@
         short 'c',
         help "Fail if formatting is not idempotent"
       ]
+    <*> (option parseBoundedEnum . mconcat)
+      [ long "color",
+        metavar "WHEN",
+        value Auto,
+        help "Colorize the output; WHEN can be 'never', 'always', or 'auto' (the default)"
+      ]
     <*> ( RegionIndices
             <$> (optional . option auto . mconcat)
               [ long "start-line",
@@ -279,7 +343,9 @@
     (optional . option auto . mconcat)
       [ long "newlines-between-decls",
         metavar "HEIGHT",
-        help "Number of spaces between top-level declarations (default 1)"
+        help $
+          "Number of spaces between top-level declarations"
+            <> showDefaultValue poNewlinesBetweenDecls
       ]
   pure PrinterOpts {..}
 
@@ -340,6 +406,11 @@
   toCLIArgument InPlace = "inplace"
   toCLIArgument Check = "check"
 
+instance ToCLIArgument ColorMode where
+  toCLIArgument Never = "never"
+  toCLIArgument Always = "always"
+  toCLIArgument Auto = "auto"
+
 showAllValues :: forall a. (Enum a, Bounded a, ToCLIArgument a) => String
 showAllValues = format (map toCLIArgument' [(minBound :: a) ..])
   where
@@ -393,3 +464,8 @@
       }
   where
     printDebug = when (cfgDebug optConfig) . hPutStrLn stderr
+
+mkConfigFromCWD :: Opts -> IO (Config RegionIndices)
+mkConfigFromCWD opts = do
+  cwd <- getCurrentDirectory
+  mkConfig cwd opts
diff --git a/data/diff-tests/inputs/empty.hs b/data/diff-tests/inputs/empty.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/empty.hs
diff --git a/data/diff-tests/inputs/main-and-bar-v2.hs b/data/diff-tests/inputs/main-and-bar-v2.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-and-bar-v2.hs
@@ -0,0 +1,11 @@
+module Main (main) where
+
+main :: IO ()
+main = pure ()
+
+foo :: Int
+foo =
+  5
+
+bar :: Int
+bar = 7
diff --git a/data/diff-tests/inputs/main-and-bar.hs b/data/diff-tests/inputs/main-and-bar.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-and-bar.hs
@@ -0,0 +1,11 @@
+module Main (main) where
+
+main :: IO ()
+main = return ()
+
+foo :: Int
+foo =
+  5
+
+bar :: Int
+bar = 6
diff --git a/data/diff-tests/inputs/main-and-baz-v2.hs b/data/diff-tests/inputs/main-and-baz-v2.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-and-baz-v2.hs
@@ -0,0 +1,12 @@
+module Main (main) where
+
+main :: IO ()
+main = pure ()
+
+foo :: Int
+foo =
+  5
+   + 5
+
+bar :: Int
+bar = 7
diff --git a/data/diff-tests/inputs/main-and-baz.hs b/data/diff-tests/inputs/main-and-baz.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-and-baz.hs
@@ -0,0 +1,12 @@
+module Main (main) where
+
+main :: IO ()
+main = return ()
+
+foo :: Int
+foo =
+  5
+   + 5
+
+bar :: Int
+bar = 6
diff --git a/data/diff-tests/inputs/main-and-foo-v2.hs b/data/diff-tests/inputs/main-and-foo-v2.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-and-foo-v2.hs
@@ -0,0 +1,7 @@
+module Main (main) where
+
+main :: IO ()
+main = pure ()
+
+foo :: Int
+foo = 5
diff --git a/data/diff-tests/inputs/main-and-foo.hs b/data/diff-tests/inputs/main-and-foo.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-and-foo.hs
@@ -0,0 +1,7 @@
+module Main (main) where
+
+main :: IO ()
+main = return ()
+
+foo :: Int
+foo = 5
diff --git a/data/diff-tests/inputs/main-foo.hs b/data/diff-tests/inputs/main-foo.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-foo.hs
@@ -0,0 +1,4 @@
+module Main (foo) where
+
+main :: IO ()
+main = return ()
diff --git a/data/diff-tests/inputs/main-v2.hs b/data/diff-tests/inputs/main-v2.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main-v2.hs
@@ -0,0 +1,4 @@
+module Main (main) where
+
+main :: IO ()
+main = pure ()
diff --git a/data/diff-tests/inputs/main.hs b/data/diff-tests/inputs/main.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/main.hs
@@ -0,0 +1,4 @@
+module Main (main) where
+
+main :: IO ()
+main = return ()
diff --git a/data/diff-tests/inputs/no-trailing-blank-line.hs b/data/diff-tests/inputs/no-trailing-blank-line.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/no-trailing-blank-line.hs
@@ -0,0 +1,1 @@
+data Test = Test
diff --git a/data/diff-tests/inputs/one-line.hs b/data/diff-tests/inputs/one-line.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/one-line.hs
@@ -0,0 +1,1 @@
+module Main (main) where
diff --git a/data/diff-tests/inputs/spaced-v2.hs b/data/diff-tests/inputs/spaced-v2.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/spaced-v2.hs
@@ -0,0 +1,16 @@
+module Main (main) where
+
+
+
+
+
+main :: IO ()
+main = pure ()
+
+
+
+
+
+
+foo :: Int
+foo = 5
diff --git a/data/diff-tests/inputs/spaced.hs b/data/diff-tests/inputs/spaced.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/spaced.hs
@@ -0,0 +1,16 @@
+module Main (main) where
+
+
+
+
+
+main :: IO ()
+main = return ()
+
+
+
+
+
+
+foo :: Int
+foo = 5
diff --git a/data/diff-tests/inputs/with-trailing-blank-line.hs b/data/diff-tests/inputs/with-trailing-blank-line.hs
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/inputs/with-trailing-blank-line.hs
@@ -0,0 +1,1 @@
+data Test = Test
diff --git a/data/diff-tests/outputs/joined-hunk.txt b/data/diff-tests/outputs/joined-hunk.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/joined-hunk.txt
@@ -0,0 +1,15 @@
+TEST
+@@ -1,11 +1,11 @@
+  module Main (main) where
+
+  main :: IO ()
+- main = return ()
++ main = pure ()
+
+  foo :: Int
+  foo =
+    5
+
+  bar :: Int
+- bar = 6
++ bar = 7
diff --git a/data/diff-tests/outputs/no-following.txt b/data/diff-tests/outputs/no-following.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/no-following.txt
@@ -0,0 +1,7 @@
+TEST
+@@ -1,4 +1,4 @@
+  module Main (main) where
+
+  main :: IO ()
+- main = return ()
++ main = pure ()
diff --git a/data/diff-tests/outputs/no-preceding.txt b/data/diff-tests/outputs/no-preceding.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/no-preceding.txt
@@ -0,0 +1,6 @@
+TEST
+@@ -1,3 +1,3 @@
+- module Main (foo) where
++ module Main (main) where
+  main :: IO ()
+  main = return ()
diff --git a/data/diff-tests/outputs/one-line-added.txt b/data/diff-tests/outputs/one-line-added.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/one-line-added.txt
@@ -0,0 +1,3 @@
+TEST
+@@ -1,0 +1,1 @@
++ module Main (main) where
diff --git a/data/diff-tests/outputs/one-line-removed.txt b/data/diff-tests/outputs/one-line-removed.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/one-line-removed.txt
@@ -0,0 +1,3 @@
+TEST
+@@ -1,1 +1,0 @@
+- module Main (main) where
diff --git a/data/diff-tests/outputs/simple-hunk.txt b/data/diff-tests/outputs/simple-hunk.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/simple-hunk.txt
@@ -0,0 +1,9 @@
+TEST
+@@ -1,6 +1,6 @@
+  module Main (main) where
+
+  main :: IO ()
+- main = return ()
++ main = pure ()
+  foo :: Int
+  foo = 5
diff --git a/data/diff-tests/outputs/trailing-blank-line.txt b/data/diff-tests/outputs/trailing-blank-line.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/trailing-blank-line.txt
@@ -0,0 +1,4 @@
+TEST
+@@ -1,1 +1,2 @@
+  data Test = Test
++
diff --git a/data/diff-tests/outputs/trimming.txt b/data/diff-tests/outputs/trimming.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/trimming.txt
@@ -0,0 +1,5 @@
+TEST
+@@ -7,2 +7,2 @@
+  main :: IO ()
+- main = return ()
++ main = pure ()
diff --git a/data/diff-tests/outputs/two-hunks.txt b/data/diff-tests/outputs/two-hunks.txt
new file mode 100644
--- /dev/null
+++ b/data/diff-tests/outputs/two-hunks.txt
@@ -0,0 +1,16 @@
+TEST
+@@ -1,7 +1,7 @@
+  module Main (main) where
+
+  main :: IO ()
+- main = return ()
++ main = pure ()
+
+  foo :: Int
+  foo =
+@@ -9,4 +9,4 @@
+     + 5
+
+  bar :: Int
+- bar = 6
++ bar = 7
diff --git a/data/examples/declaration/class/newlines-and-haddocks-four-out.hs b/data/examples/declaration/class/newlines-and-haddocks-four-out.hs
--- a/data/examples/declaration/class/newlines-and-haddocks-four-out.hs
+++ b/data/examples/declaration/class/newlines-and-haddocks-four-out.hs
@@ -21,3 +21,7 @@
 
     -- | Haddock followed by a blank line
     abc :: a
+
+    -- | A multiline
+    -- comment here
+    multiline :: a
diff --git a/data/examples/declaration/class/newlines-and-haddocks-out.hs b/data/examples/declaration/class/newlines-and-haddocks-out.hs
--- a/data/examples/declaration/class/newlines-and-haddocks-out.hs
+++ b/data/examples/declaration/class/newlines-and-haddocks-out.hs
@@ -21,3 +21,7 @@
 
   -- | Haddock followed by a blank line
   abc :: a
+
+  -- | A multiline
+  -- comment here
+  multiline :: a
diff --git a/data/examples/declaration/class/newlines-and-haddocks.hs b/data/examples/declaration/class/newlines-and-haddocks.hs
--- a/data/examples/declaration/class/newlines-and-haddocks.hs
+++ b/data/examples/declaration/class/newlines-and-haddocks.hs
@@ -17,3 +17,7 @@
   -- | Haddock followed by a blank line
 
   abc :: a
+
+  -- | A multiline
+  -- comment here
+  multiline :: a
diff --git a/data/examples/declaration/data/ctype-four-out.hs b/data/examples/declaration/data/ctype-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype-four-out.hs
@@ -0,0 +1,1 @@
+data {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/ctype-out.hs b/data/examples/declaration/data/ctype-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype-out.hs
@@ -0,0 +1,1 @@
+data {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/ctype.hs b/data/examples/declaration/data/ctype.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/ctype.hs
@@ -0,0 +1,1 @@
+data    {-# CTYPE "unistd.h" "useconds_t" #-} T
diff --git a/data/examples/declaration/data/field-layout/gadt-four-out.hs b/data/examples/declaration/data/field-layout/gadt-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/gadt-four-out.hs
@@ -0,0 +1,11 @@
+module Main where
+
+-- | Foo.
+data Foo a b where
+    -- | Something
+    Foo :: Foo Int Int
+    -- | Something else
+    Bar ::
+        Foo
+            Char
+            Char
diff --git a/data/examples/declaration/data/field-layout/gadt-out.hs b/data/examples/declaration/data/field-layout/gadt-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/gadt-out.hs
@@ -0,0 +1,11 @@
+module Main where
+
+-- | Foo.
+data Foo a b where
+  -- | Something
+  Foo :: Foo Int Int
+  -- | Something else
+  Bar ::
+    Foo
+      Char
+      Char
diff --git a/data/examples/declaration/data/field-layout/gadt.hs b/data/examples/declaration/data/field-layout/gadt.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/gadt.hs
@@ -0,0 +1,10 @@
+module Main where
+
+-- | Foo.
+
+data Foo a b where
+  -- | Something
+  Foo :: Foo Int Int
+  -- | Something else
+  Bar :: Foo Char
+    Char
diff --git a/data/examples/declaration/data/field-layout/record-four-out.hs b/data/examples/declaration/data/field-layout/record-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-four-out.hs
@@ -0,0 +1,12 @@
+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-out.hs b/data/examples/declaration/data/field-layout/record-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record-out.hs
@@ -0,0 +1,12 @@
+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
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/record.hs
@@ -0,0 +1,11 @@
+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/field-layout/variants-four-out.hs b/data/examples/declaration/data/field-layout/variants-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/variants-four-out.hs
@@ -0,0 +1,10 @@
+module Main where
+
+-- | Foo.
+data Foo
+    = -- | Something
+      Foo Int Int
+    | -- | Something else
+      Bar
+        Char
+        Char
diff --git a/data/examples/declaration/data/field-layout/variants-out.hs b/data/examples/declaration/data/field-layout/variants-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/variants-out.hs
@@ -0,0 +1,10 @@
+module Main where
+
+-- | Foo.
+data Foo
+  = -- | Something
+    Foo Int Int
+  | -- | Something else
+    Bar
+      Char
+      Char
diff --git a/data/examples/declaration/data/field-layout/variants.hs b/data/examples/declaration/data/field-layout/variants.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/data/field-layout/variants.hs
@@ -0,0 +1,10 @@
+module Main where
+
+-- | Foo.
+
+data Foo =
+  Foo Int Int
+  -- ^ Something
+  | Bar Char
+        Char
+  -- ^ Something else
diff --git a/data/examples/declaration/data/gadt/multiline-where-four-out.hs b/data/examples/declaration/data/gadt/multiline-where-four-out.hs
--- a/data/examples/declaration/data/gadt/multiline-where-four-out.hs
+++ b/data/examples/declaration/data/gadt/multiline-where-four-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-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/instance/newlines-between-methods-four-out.hs b/data/examples/declaration/instance/newlines-between-methods-four-out.hs
--- a/data/examples/declaration/instance/newlines-between-methods-four-out.hs
+++ b/data/examples/declaration/instance/newlines-between-methods-four-out.hs
@@ -4,7 +4,7 @@
     D u dudx * D v dvdx = D (u * v) (u * dvdx + v * dudx)
 
     -- Comment before definition
-    negate (D u dudx) = D (- u) (- dudx)
+    negate (D u dudx) = D (-u) (-dudx)
     negate (Z u dudx) = undefined
 
     -- Comment after definition
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
@@ -4,7 +4,7 @@
   D u dudx * D v dvdx = D (u * v) (u * dvdx + v * dudx)
 
   -- Comment before definition
-  negate (D u dudx) = D (- u) (- dudx)
+  negate (D u dudx) = D (-u) (-dudx)
   negate (Z u dudx) = undefined
 
   -- Comment after definition
diff --git a/data/examples/declaration/signature/standalone-kind-four-out.hs b/data/examples/declaration/signature/standalone-kind-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/standalone-kind-four-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE StandaloneKindSignatures #-}
+
+type T ::
+    (k -> Type) ->
+    k ->
+    Type
+data T m a = MkT (m a) (T Maybe (m a))
+
+type C1 :: Type -> Constraint
+class C1 a
+
+type F :: Type -> Type
+type family F
+
+type G :: Type
+type G = a
diff --git a/data/examples/declaration/signature/standalone-kind-out.hs b/data/examples/declaration/signature/standalone-kind-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/standalone-kind-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE StandaloneKindSignatures #-}
+
+type T ::
+  (k -> Type) ->
+  k ->
+  Type
+data T m a = MkT (m a) (T Maybe (m a))
+
+type C1 :: Type -> Constraint
+class C1 a
+
+type F :: Type -> Type
+type family F
+
+type G :: Type
+type G = a
diff --git a/data/examples/declaration/signature/standalone-kind.hs b/data/examples/declaration/signature/standalone-kind.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/signature/standalone-kind.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE StandaloneKindSignatures #-}
+
+type T :: (k -> Type)
+       -> k
+       -> Type
+
+data T m a = MkT (m a) (T Maybe (m a))
+
+type C1 :: Type -> Constraint
+class C1 a
+
+type F :: Type -> Type
+type family F
+
+type G :: Type
+
+type G = a
diff --git a/data/examples/declaration/splice/bracket-declaration-four-out.hs b/data/examples/declaration/splice/bracket-declaration-four-out.hs
--- a/data/examples/declaration/splice/bracket-declaration-four-out.hs
+++ b/data/examples/declaration/splice/bracket-declaration-four-out.hs
@@ -24,3 +24,12 @@
             deriving (Eq, Ord, Enum, Bounded, Show)
         |]
  )
+
+foo = [d| type X = * |]
+
+foo =
+    [d|
+        type X = *
+
+        data A
+        |]
diff --git a/data/examples/declaration/splice/bracket-declaration-out.hs b/data/examples/declaration/splice/bracket-declaration-out.hs
--- a/data/examples/declaration/splice/bracket-declaration-out.hs
+++ b/data/examples/declaration/splice/bracket-declaration-out.hs
@@ -24,3 +24,12 @@
         deriving (Eq, Ord, Enum, Bounded, Show)
       |]
  )
+
+foo = [d| type X = * |]
+
+foo =
+  [d|
+    type X = *
+
+    data A
+    |]
diff --git a/data/examples/declaration/splice/bracket-declaration.hs b/data/examples/declaration/splice/bracket-declaration.hs
--- a/data/examples/declaration/splice/bracket-declaration.hs
+++ b/data/examples/declaration/splice/bracket-declaration.hs
@@ -20,3 +20,11 @@
   data T = T
     deriving (Eq, Ord, Enum, Bounded, Show)
  |])
+
+
+foo = [d|type X = * |]
+
+foo = [d|
+  type X = *
+  data A
+        |]
diff --git a/data/examples/declaration/splice/bracket-four-out.hs b/data/examples/declaration/splice/bracket-four-out.hs
--- a/data/examples/declaration/splice/bracket-four-out.hs
+++ b/data/examples/declaration/splice/bracket-four-out.hs
@@ -16,3 +16,7 @@
     [||
     foo bar
     ||]
+
+foo = [t| * |]
+
+foo = [t| a -> * -> a |]
diff --git a/data/examples/declaration/splice/bracket-out.hs b/data/examples/declaration/splice/bracket-out.hs
--- a/data/examples/declaration/splice/bracket-out.hs
+++ b/data/examples/declaration/splice/bracket-out.hs
@@ -16,3 +16,7 @@
   [||
   foo bar
   ||]
+
+foo = [t| * |]
+
+foo = [t| a -> * -> a |]
diff --git a/data/examples/declaration/splice/bracket.hs b/data/examples/declaration/splice/bracket.hs
--- a/data/examples/declaration/splice/bracket.hs
+++ b/data/examples/declaration/splice/bracket.hs
@@ -10,3 +10,7 @@
 
 foo = [|| foo bar
   ||]
+
+foo = [t| * |]
+
+foo = [t|a -> * -> a|]
diff --git a/data/examples/declaration/splice/grouped-splices-four-out.hs b/data/examples/declaration/splice/grouped-splices-four-out.hs
--- a/data/examples/declaration/splice/grouped-splices-four-out.hs
+++ b/data/examples/declaration/splice/grouped-splices-four-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TemplateHaskell #-}
+
 $(deriveJSON fieldLabelMod ''A)
 $(deriveJSON fieldLabelMod ''B)
 $(deriveJSON fieldLabelMod ''C)
diff --git a/data/examples/declaration/splice/grouped-splices-out.hs b/data/examples/declaration/splice/grouped-splices-out.hs
--- a/data/examples/declaration/splice/grouped-splices-out.hs
+++ b/data/examples/declaration/splice/grouped-splices-out.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TemplateHaskell #-}
+
 $(deriveJSON fieldLabelMod ''A)
 $(deriveJSON fieldLabelMod ''B)
 $(deriveJSON fieldLabelMod ''C)
diff --git a/data/examples/declaration/splice/grouped-splices.hs b/data/examples/declaration/splice/grouped-splices.hs
--- a/data/examples/declaration/splice/grouped-splices.hs
+++ b/data/examples/declaration/splice/grouped-splices.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE TemplateHaskell #-}
+
 $(deriveJSON fieldLabelMod ''A)
 $(deriveJSON fieldLabelMod ''B)
 $(deriveJSON fieldLabelMod ''C)
diff --git a/data/examples/declaration/splice/quasiquote-four-out.hs b/data/examples/declaration/splice/quasiquote-four-out.hs
--- a/data/examples/declaration/splice/quasiquote-four-out.hs
+++ b/data/examples/declaration/splice/quasiquote-four-out.hs
@@ -10,3 +10,7 @@
 [d|    foo bar
 
 |]
+
+header =
+    [here|
+#include foo|]
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/type-families/closed-type-family/type-arguments-four-out.hs b/data/examples/declaration/type-families/closed-type-family/type-arguments-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/type-arguments-four-out.hs
@@ -0,0 +1,4 @@
+type PickType :: forall k. Nat -> k
+type family PickType n where
+    PickType @Type 1 = Bool
+    PickType @(Type -> Type) 2 = Maybe
diff --git a/data/examples/declaration/type-families/closed-type-family/type-arguments-out.hs b/data/examples/declaration/type-families/closed-type-family/type-arguments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/type-arguments-out.hs
@@ -0,0 +1,4 @@
+type PickType :: forall k. Nat -> k
+type family PickType n where
+  PickType @Type 1 = Bool
+  PickType @(Type -> Type) 2 = Maybe
diff --git a/data/examples/declaration/type-families/closed-type-family/type-arguments.hs b/data/examples/declaration/type-families/closed-type-family/type-arguments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/closed-type-family/type-arguments.hs
@@ -0,0 +1,4 @@
+type PickType :: forall k. Nat -> k
+type family PickType n where
+  PickType @Type 1 = Bool
+  PickType @(Type -> Type) 2 = Maybe
diff --git a/data/examples/declaration/type-families/data-family/type-arguments-four-out.hs b/data/examples/declaration/type-families/data-family/type-arguments-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/data-family/type-arguments-four-out.hs
@@ -0,0 +1,5 @@
+type PickType :: forall k. (k -> Type) -> Type
+data family PickType m
+
+data instance PickType @Nat M where
+    Foo :: PickType M
diff --git a/data/examples/declaration/type-families/data-family/type-arguments-out.hs b/data/examples/declaration/type-families/data-family/type-arguments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/data-family/type-arguments-out.hs
@@ -0,0 +1,5 @@
+type PickType :: forall k. (k -> Type) -> Type
+data family PickType m
+
+data instance PickType @Nat M where
+  Foo :: PickType M
diff --git a/data/examples/declaration/type-families/data-family/type-arguments.hs b/data/examples/declaration/type-families/data-family/type-arguments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type-families/data-family/type-arguments.hs
@@ -0,0 +1,5 @@
+type PickType :: forall k. (k -> Type) -> Type
+data family PickType m
+
+data instance PickType @Nat M where
+  Foo :: PickType M
diff --git a/data/examples/declaration/type/misc-kind-signatures-four-out.hs b/data/examples/declaration/type/misc-kind-signatures-four-out.hs
--- a/data/examples/declaration/type/misc-kind-signatures-four-out.hs
+++ b/data/examples/declaration/type/misc-kind-signatures-four-out.hs
@@ -3,3 +3,8 @@
 
 b :: (Bool :: *)
 b = True
+
+unP :: forall {k} (a :: k). P a -> Proxy a
+
+data ProxyKInvis (a :: k) :: forall k. k -> Type
+data ProxyKVis k (a :: k) :: forall k -> k -> Type
diff --git a/data/examples/declaration/type/misc-kind-signatures-out.hs b/data/examples/declaration/type/misc-kind-signatures-out.hs
--- a/data/examples/declaration/type/misc-kind-signatures-out.hs
+++ b/data/examples/declaration/type/misc-kind-signatures-out.hs
@@ -3,3 +3,9 @@
 
 b :: (Bool :: *)
 b = True
+
+unP :: forall {k} (a :: k). P a -> Proxy a
+
+data ProxyKInvis (a :: k) :: forall k. k -> Type
+
+data ProxyKVis k (a :: k) :: forall k -> k -> Type
diff --git a/data/examples/declaration/type/misc-kind-signatures.hs b/data/examples/declaration/type/misc-kind-signatures.hs
--- a/data/examples/declaration/type/misc-kind-signatures.hs
+++ b/data/examples/declaration/type/misc-kind-signatures.hs
@@ -3,3 +3,8 @@
 
 b :: (Bool :: *)
 b = True
+
+unP :: forall {k} (a :: k). P a -> Proxy a
+
+data ProxyKInvis (a :: k) :: forall k. k -> Type
+data ProxyKVis k (a :: k) :: forall k -> k -> Type
diff --git a/data/examples/declaration/type/promotion-0-four-out.hs b/data/examples/declaration/type/promotion-0-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-0-four-out.hs
@@ -0,0 +1,9 @@
+type Foo = Cluster '[ 'NodeCore, 'NodeRelay', 'NodeEdge]
+
+type Query = Query '[] '[] DB '[ 'NotNull 'PGint4] (RowPG RawElementData)
+
+data T = T' | T'T
+
+type S0 = ' T'
+
+type S1 = ' T'T
diff --git a/data/examples/declaration/type/promotion-0-out.hs b/data/examples/declaration/type/promotion-0-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-0-out.hs
@@ -0,0 +1,9 @@
+type Foo = Cluster '[ 'NodeCore, 'NodeRelay', 'NodeEdge]
+
+type Query = Query '[] '[] DB '[ 'NotNull 'PGint4] (RowPG RawElementData)
+
+data T = T' | T'T
+
+type S0 = ' T'
+
+type S1 = ' T'T
diff --git a/data/examples/declaration/type/promotion-0.hs b/data/examples/declaration/type/promotion-0.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-0.hs
@@ -0,0 +1,9 @@
+type Foo = Cluster '[ 'NodeCore, 'NodeRelay', 'NodeEdge ]
+
+type Query = Query '[] '[] DB '[ 'NotNull 'PGint4] (RowPG RawElementData)
+
+data T = T' | T'T
+
+type S0 = ' T'
+
+type S1 = ' T'T
diff --git a/data/examples/declaration/type/promotion-1-four-out.hs b/data/examples/declaration/type/promotion-1-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-1-four-out.hs
@@ -0,0 +1,9 @@
+type S2 = Proxy ('[ '[], '[]])
+type S4 = Proxy ('( 'Just, ' T'T))
+type S5 = Proxy ('[ 'Just, 'TT'T])
+type S6 = Proxy ('( '(), '()))
+type S7 = Proxy ('( 'a, 'b))
+type S8 = Proxy ('[Int, Bool])
+
+type E = TypeError ( 'Text "Some text")
+type G = '[ '( 'Just, 'Bool)]
diff --git a/data/examples/declaration/type/promotion-1-out.hs b/data/examples/declaration/type/promotion-1-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-1-out.hs
@@ -0,0 +1,15 @@
+type S2 = Proxy ('[ '[], '[]])
+
+type S4 = Proxy ('( 'Just, ' T'T))
+
+type S5 = Proxy ('[ 'Just, 'TT'T])
+
+type S6 = Proxy ('( '(), '()))
+
+type S7 = Proxy ('( 'a, 'b))
+
+type S8 = Proxy ('[Int, Bool])
+
+type E = TypeError ( 'Text "Some text")
+
+type G = '[ '( 'Just, 'Bool)]
diff --git a/data/examples/declaration/type/promotion-1.hs b/data/examples/declaration/type/promotion-1.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/type/promotion-1.hs
@@ -0,0 +1,9 @@
+type S2 = Proxy ( '[ '[], '[] ])
+type S4 = Proxy ( '( 'Just, ' T'T))
+type S5 = Proxy ( '[ 'Just, ' TT'T])
+type S6 = Proxy ( '( '(), '() ))
+type S7 = Proxy ( '( 'a, 'b ))
+type S8 = Proxy ( '[ Int, Bool ])
+
+type E = TypeError ('Text "Some text")
+type G = '[ '( 'Just, 'Bool) ]
diff --git a/data/examples/declaration/type/promotion-four-out.hs b/data/examples/declaration/type/promotion-four-out.hs
deleted file mode 100644
--- a/data/examples/declaration/type/promotion-four-out.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-type Foo = Cluster '[ 'NodeCore, 'NodeRelay', 'NodeEdge]
-
-type Query = Query '[] '[] DB '[ 'NotNull 'PGint4] (RowPG RawElementData)
-
-data T = T' | T'T
-
-type S0 = ' T'
-
-type S1 = ' T'T
-
-type S2 = Proxy ('[ '[], '[]])
-type S4 = Proxy ('( 'Just, ' T'T))
-type S5 = Proxy ('[ 'Just, 'TT'T])
-type S6 = Proxy ('( '(), '()))
-type S7 = Proxy ('( 'a, 'b))
-type S8 = Proxy ('[Int, Bool])
diff --git a/data/examples/declaration/type/promotion-out.hs b/data/examples/declaration/type/promotion-out.hs
deleted file mode 100644
--- a/data/examples/declaration/type/promotion-out.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-type Foo = Cluster '[ 'NodeCore, 'NodeRelay', 'NodeEdge]
-
-type Query = Query '[] '[] DB '[ 'NotNull 'PGint4] (RowPG RawElementData)
-
-data T = T' | T'T
-
-type S0 = ' T'
-
-type S1 = ' T'T
-
-type S2 = Proxy ('[ '[], '[]])
-
-type S4 = Proxy ('( 'Just, ' T'T))
-
-type S5 = Proxy ('[ 'Just, 'TT'T])
-
-type S6 = Proxy ('( '(), '()))
-
-type S7 = Proxy ('( 'a, 'b))
-
-type S8 = Proxy ('[Int, Bool])
diff --git a/data/examples/declaration/type/promotion.hs b/data/examples/declaration/type/promotion.hs
deleted file mode 100644
--- a/data/examples/declaration/type/promotion.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-type Foo = Cluster '[ 'NodeCore, 'NodeRelay', 'NodeEdge ]
-
-type Query = Query '[] '[] DB '[ 'NotNull 'PGint4] (RowPG RawElementData)
-
-data T = T' | T'T
-
-type S0 = ' T'
-
-type S1 = ' T'T
-
-type S2 = Proxy ( '[ '[], '[] ])
-type S4 = Proxy ( '( 'Just, ' T'T))
-type S5 = Proxy ( '[ 'Just, ' TT'T])
-type S6 = Proxy ( '( '(), '() ))
-type S7 = Proxy ( '( 'a, 'b ))
-type S8 = Proxy ( '[ Int, Bool ])
diff --git a/data/examples/declaration/value/function/application-1-four-out.hs b/data/examples/declaration/value/function/application-1-four-out.hs
--- a/data/examples/declaration/value/function/application-1-four-out.hs
+++ b/data/examples/declaration/value/function/application-1-four-out.hs
@@ -2,18 +2,18 @@
     do
         x
         y
-    z
+      z
 
 main =
     case foo of
         x -> a
-    foo
-    a
-    b
+      foo
+      a
+      b
 
 main =
     do
         if x then y else z
-    foo
-    a
-    b
+      foo
+      a
+      b
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
@@ -2,18 +2,18 @@
   do
     x
     y
-  z
+   z
 
 main =
   case foo of
     x -> a
-  foo
-  a
-  b
+   foo
+   a
+   b
 
 main =
   do
     if x then y else z
-  foo
-  a
-  b
+   foo
+   a
+   b
diff --git a/data/examples/declaration/value/function/application-2-four-out.hs b/data/examples/declaration/value/function/application-2-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/application-2-four-out.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+foo = do
+    $( bar
+     )
+        baz
+
+foo = do
+    $$( bar
+      )
+        baz
+
+foo = do
+    do (+ 1)
+      2
+
+foo = do
+    do
+        (+ 1)
+      2
+
+foo = do
+    case () of () -> (+ 1)
+        2
+
+foo = do
+    case () of
+        () -> (+ 1)
+      2
+
+foo = do
+    \case 2 -> 3
+        2
+
+foo = do
+    \case
+        2 -> 3
+      2
diff --git a/data/examples/declaration/value/function/application-2-out.hs b/data/examples/declaration/value/function/application-2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/application-2-out.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+foo = do
+  $( bar
+   )
+    baz
+
+foo = do
+  $$( bar
+    )
+    baz
+
+foo = do
+  do (+ 1)
+    2
+
+foo = do
+  do
+    (+ 1)
+   2
+
+foo = do
+  case () of () -> (+ 1)
+    2
+
+foo = do
+  case () of
+    () -> (+ 1)
+   2
+
+foo = do
+  \case 2 -> 3
+    2
+
+foo = do
+  \case
+    2 -> 3
+   2
diff --git a/data/examples/declaration/value/function/application-2.hs b/data/examples/declaration/value/function/application-2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/application-2.hs
@@ -0,0 +1,36 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+foo = do
+  $( bar
+   ) baz
+
+foo = do
+  $$( bar
+    ) baz
+
+foo = do
+  do (+1)
+   2
+
+foo = do
+  do
+    (+1)
+   2
+
+foo = do
+  case () of () -> (+1)
+   2
+
+foo = do
+  case () of
+    () -> (+1)
+   2
+
+foo = do
+  \case 2 -> 3
+   2
+
+foo = do
+  \case
+    2 -> 3
+   2
diff --git a/data/examples/declaration/value/function/arrow/proc-applications-four-out.hs b/data/examples/declaration/value/function/arrow/proc-applications-four-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-applications-four-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-applications-four-out.hs
@@ -2,6 +2,8 @@
 
 foo x = proc a -> a -< x
 
+foolr x = proc a -> x >- a
+
 bar f x =
     proc
         ( y
@@ -17,3 +19,5 @@
                 )
 
 baz x = proc a -> a -<< x
+
+bazlr x = proc a -> x >>- a
diff --git a/data/examples/declaration/value/function/arrow/proc-applications-out.hs b/data/examples/declaration/value/function/arrow/proc-applications-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-applications-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-applications-out.hs
@@ -2,6 +2,8 @@
 
 foo x = proc a -> a -< x
 
+foolr x = proc a -> x >- a
+
 bar f x =
   proc
     ( y,
@@ -17,3 +19,5 @@
         )
 
 baz x = proc a -> a -<< x
+
+bazlr x = proc a -> x >>- a
diff --git a/data/examples/declaration/value/function/arrow/proc-applications.hs b/data/examples/declaration/value/function/arrow/proc-applications.hs
--- a/data/examples/declaration/value/function/arrow/proc-applications.hs
+++ b/data/examples/declaration/value/function/arrow/proc-applications.hs
@@ -2,6 +2,8 @@
 
 foo x = proc a -> a -< x
 
+foolr x = proc a -> x >- a
+
 bar f x =
     proc (
         y,
@@ -15,3 +17,5 @@
     )
 
 baz x = proc a -> a -<< x
+
+bazlr x = proc a -> x >>- a
diff --git a/data/examples/declaration/value/function/arrow/proc-applications2-four-out.hs b/data/examples/declaration/value/function/arrow/proc-applications2-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/arrow/proc-applications2-four-out.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE Arrows #-}
+
+t = proc ys ->
+    (| f (\y -> returnA -< y) |) ys
+
+g x = proc (y, z) ->
+    ( case compare x y of
+        LT -> \a -> returnA -< x + a
+        EQ -> \b -> returnA -< y + z + b
+        GT -> \c -> returnA -< z + x
+    ) 1
diff --git a/data/examples/declaration/value/function/arrow/proc-applications2-out.hs b/data/examples/declaration/value/function/arrow/proc-applications2-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/arrow/proc-applications2-out.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE Arrows #-}
+
+t = proc ys ->
+  (| f (\y -> returnA -< y) |) ys
+
+g x = proc (y, z) ->
+  ( case compare x y of
+      LT -> \a -> returnA -< x + a
+      EQ -> \b -> returnA -< y + z + b
+      GT -> \c -> returnA -< z + x
+  ) 1
diff --git a/data/examples/declaration/value/function/arrow/proc-applications2.hs b/data/examples/declaration/value/function/arrow/proc-applications2.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/arrow/proc-applications2.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE Arrows #-}
+
+t = proc ys ->
+  (| f (\y -> returnA -< y) |) ys
+
+g x = proc (y, z) ->
+  ( case compare x y of
+      LT -> \a -> returnA -< x + a
+      EQ -> \b -> returnA -< y + z + b
+      GT -> \c -> returnA -< z + x
+  ) 1
diff --git a/data/examples/declaration/value/function/arrow/proc-cases-four-out.hs b/data/examples/declaration/value/function/arrow/proc-cases-four-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-cases-four-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-cases-four-out.hs
@@ -14,3 +14,8 @@
         Right
             (Right b) ->
                 j -< b
+
+baz = proc x ->
+    (f -< x) `catchA` \case
+        Right a -> f -< a
+        Left e -> h -< e
diff --git a/data/examples/declaration/value/function/arrow/proc-cases-out.hs b/data/examples/declaration/value/function/arrow/proc-cases-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-cases-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-cases-out.hs
@@ -14,3 +14,8 @@
     Right
       (Right b) ->
         j -< b
+
+baz = proc x ->
+  (f -< x) `catchA` \case
+    Right a -> f -< a
+    Left e -> h -< e
diff --git a/data/examples/declaration/value/function/arrow/proc-cases.hs b/data/examples/declaration/value/function/arrow/proc-cases.hs
--- a/data/examples/declaration/value/function/arrow/proc-cases.hs
+++ b/data/examples/declaration/value/function/arrow/proc-cases.hs
@@ -15,3 +15,8 @@
         Right
           (Right b) ->
             j -< b
+
+
+baz = proc x -> (f -< x) `catchA`  \case
+                    Right a -> f -< a
+                    Left e  -> h -< e
diff --git a/data/examples/declaration/value/function/arrow/proc-do-complex-four-out.hs b/data/examples/declaration/value/function/arrow/proc-do-complex-four-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-complex-four-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-complex-four-out.hs
@@ -60,3 +60,6 @@
                                 + 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-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
@@ -60,3 +60,6 @@
                   + 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-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
@@ -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-four-out.hs b/data/examples/declaration/value/function/arrow/proc-do-simple1-four-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-simple1-four-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-simple1-four-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
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
diff --git a/data/examples/declaration/value/function/arrow/proc-do-simple2-four-out.hs b/data/examples/declaration/value/function/arrow/proc-do-simple2-four-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-do-simple2-four-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-do-simple2-four-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-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-four-out.hs b/data/examples/declaration/value/function/arrow/proc-form-do-indent-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/arrow/proc-form-do-indent-four-out.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE Arrows #-}
+
+foo x = proc (y, z) -> do
+    (|
+        bar
+            (bindA -< y)
+        |)
+
+foo1 x = proc (y, z) -> do
+    (|
+        bar
+            (bindA -< y)
+        |) z
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
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/arrow/proc-form-do-indent-out.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE Arrows #-}
+
+foo x = proc (y, z) -> do
+  (|
+    bar
+      (bindA -< y)
+    |)
+
+foo1 x = proc (y, z) -> do
+  (|
+    bar
+      (bindA -< y)
+    |) z
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
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/arrow/proc-form-do-indent.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE Arrows #-}
+
+foo x = proc (y, z) -> do
+  (|
+    bar
+      (bindA -< y)
+    |)
+
+foo1 x = proc (y, z) -> do
+  (|
+    bar
+      (bindA -< y)
+    |) z
diff --git a/data/examples/declaration/value/function/arrow/proc-forms2-four-out.hs b/data/examples/declaration/value/function/arrow/proc-forms2-four-out.hs
--- a/data/examples/declaration/value/function/arrow/proc-forms2-four-out.hs
+++ b/data/examples/declaration/value/function/arrow/proc-forms2-four-out.hs
@@ -15,8 +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
+
+expr' = proc x ->
+    do
+        returnA -< x
+        <+> 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-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,8 +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
+
+expr' = proc x ->
+  do
+    returnA -< x
+    <+> 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
@@ -17,3 +17,25 @@
   |||
     ((h g  .  h f)
       -<y z  .  (y x))
+
+bar4 = proc x -> case x of
+    Just f -> f -< ()
+    Nothing -> 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
+
+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/block-arguments-four-out.hs b/data/examples/declaration/value/function/block-arguments-four-out.hs
--- a/data/examples/declaration/value/function/block-arguments-four-out.hs
+++ b/data/examples/declaration/value/function/block-arguments-four-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-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-four-out.hs b/data/examples/declaration/value/function/case-empty-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/case-empty-four-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-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/comprehension/transform-monad-four-out.hs b/data/examples/declaration/value/function/comprehension/transform-monad-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/comprehension/transform-monad-four-out.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE MonadComprehensions #-}
+{-# LANGUAGE TransformListComp #-}
+
+quux' xs ys =
+    [ ( x
+      , y
+      )
+    | 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-monad-out.hs b/data/examples/declaration/value/function/comprehension/transform-monad-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/comprehension/transform-monad-out.hs
@@ -0,0 +1,18 @@
+{-# LANGUAGE MonadComprehensions #-}
+{-# LANGUAGE TransformListComp #-}
+
+quux' xs ys =
+  [ ( x,
+      y
+    )
+    | 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-monad.hs b/data/examples/declaration/value/function/comprehension/transform-monad.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/comprehension/transform-monad.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE MonadComprehensions #-}
+{-# LANGUAGE TransformListComp #-}
+
+quux' xs ys = [
+  (x,
+    y) |
+  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/lambda-single-line-four-out.hs b/data/examples/declaration/value/function/lambda-single-line-four-out.hs
--- a/data/examples/declaration/value/function/lambda-single-line-four-out.hs
+++ b/data/examples/declaration/value/function/lambda-single-line-four-out.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TemplateHaskell #-}
 
 foo :: a -> a -> a
 foo x = \y -> x
diff --git a/data/examples/declaration/value/function/lambda-single-line-out.hs b/data/examples/declaration/value/function/lambda-single-line-out.hs
--- a/data/examples/declaration/value/function/lambda-single-line-out.hs
+++ b/data/examples/declaration/value/function/lambda-single-line-out.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TemplateHaskell #-}
 
 foo :: a -> a -> a
 foo x = \y -> x
diff --git a/data/examples/declaration/value/function/lambda-single-line.hs b/data/examples/declaration/value/function/lambda-single-line.hs
--- a/data/examples/declaration/value/function/lambda-single-line.hs
+++ b/data/examples/declaration/value/function/lambda-single-line.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TemplateHaskell #-}
 
 foo :: a -> a -> a
 foo x = \y -> x
diff --git a/data/examples/declaration/value/function/lexical-negation-four-out.hs b/data/examples/declaration/value/function/lexical-negation-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/lexical-negation-four-out.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE LexicalNegation #-}
+
+foo = -1
+
+foo = -x
+
+weird :: (Int -> Int) -> Int -> Int
+weird x y = x -y
diff --git a/data/examples/declaration/value/function/lexical-negation-out.hs b/data/examples/declaration/value/function/lexical-negation-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/lexical-negation-out.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE LexicalNegation #-}
+
+foo = -1
+
+foo = -x
+
+weird :: (Int -> Int) -> Int -> Int
+weird x y = x -y
diff --git a/data/examples/declaration/value/function/lexical-negation.hs b/data/examples/declaration/value/function/lexical-negation.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/lexical-negation.hs
@@ -0,0 +1,8 @@
+{-# LANGUAGE LexicalNegation #-}
+
+foo = -1
+
+foo = -x
+
+weird :: (Int -> Int) -> Int -> Int
+weird x y = x -y
diff --git a/data/examples/declaration/value/function/negation-four-out.hs b/data/examples/declaration/value/function/negation-four-out.hs
--- a/data/examples/declaration/value/function/negation-four-out.hs
+++ b/data/examples/declaration/value/function/negation-four-out.hs
@@ -5,4 +5,16 @@
 bar = -2
 
 baz :: Int
-baz = - 2
+baz = -2
+
+neg :: Int -> Int
+neg x = (-x)
+neg x = -x
+neg x = -x
+
+weird :: Int -> Int -> Int
+weird x y = x - y
+
+pat = \case -1 -> 1
+
+pat = \case -1 -> 1
diff --git a/data/examples/declaration/value/function/negation-out.hs b/data/examples/declaration/value/function/negation-out.hs
--- a/data/examples/declaration/value/function/negation-out.hs
+++ b/data/examples/declaration/value/function/negation-out.hs
@@ -5,4 +5,16 @@
 bar = -2
 
 baz :: Int
-baz = - 2
+baz = -2
+
+neg :: Int -> Int
+neg x = (-x)
+neg x = -x
+neg x = -x
+
+weird :: Int -> Int -> Int
+weird x y = x - y
+
+pat = \case -1 -> 1
+
+pat = \case -1 -> 1
diff --git a/data/examples/declaration/value/function/negation.hs b/data/examples/declaration/value/function/negation.hs
--- a/data/examples/declaration/value/function/negation.hs
+++ b/data/examples/declaration/value/function/negation.hs
@@ -6,3 +6,15 @@
 
 baz :: Int
 baz = - 2
+
+neg :: Int -> Int
+neg x = (- x)
+neg x = - x
+neg x = -x
+
+weird :: Int -> Int -> Int
+weird x y = x -y
+
+pat = \case -1 -> 1
+
+pat = \case - 1 -> 1
diff --git a/data/examples/declaration/value/function/negative-literals-four-out.hs b/data/examples/declaration/value/function/negative-literals-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/negative-literals-four-out.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE NegativeLiterals #-}
+
+foo = - 1
+
+foo = -1
+
+foo = -x
+
+foo = -x
+
+pat = \case -1 -> 1
+
+pat = \case - 1 -> 1
diff --git a/data/examples/declaration/value/function/negative-literals-out.hs b/data/examples/declaration/value/function/negative-literals-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/negative-literals-out.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE NegativeLiterals #-}
+
+foo = - 1
+
+foo = -1
+
+foo = -x
+
+foo = -x
+
+pat = \case -1 -> 1
+
+pat = \case - 1 -> 1
diff --git a/data/examples/declaration/value/function/negative-literals.hs b/data/examples/declaration/value/function/negative-literals.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/negative-literals.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE NegativeLiterals #-}
+
+foo = - 1
+
+foo = -1
+
+foo = - x
+
+foo = -x
+
+pat = \case -1 -> 1
+
+pat = \case - 1 -> 1
diff --git a/data/examples/declaration/value/function/operators-7-four-out.hs b/data/examples/declaration/value/function/operators-7-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/operators-7-four-out.hs
@@ -0,0 +1,1 @@
+foo c = case c of { _ -> c } : []
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/pattern/as-pattern.hs b/data/examples/declaration/value/function/pattern/as-pattern.hs
--- a/data/examples/declaration/value/function/pattern/as-pattern.hs
+++ b/data/examples/declaration/value/function/pattern/as-pattern.hs
@@ -1,3 +1,3 @@
 main = case [1] of
-  xs @ (x:_) -> print (x, xs)
+  xs@(x:_) -> print (x, xs)
   xs@[] -> print xs
diff --git a/data/examples/declaration/value/function/pragmas-four-out.hs b/data/examples/declaration/value/function/pragmas-four-out.hs
--- a/data/examples/declaration/value/function/pragmas-four-out.hs
+++ b/data/examples/declaration/value/function/pragmas-four-out.hs
@@ -3,7 +3,9 @@
     {-# SCC "barbaz" #-}
     "hello"
 
-corefoo = {-# CORE "foo" #-} 1
+-- CORE pragma got removed in https://gitlab.haskell.org/ghc/ghc/-/commit/12f9035200424ec8104484f154a040d612fee99d
+
+corefoo = {-# CORE "foo"#-} 1
 corebar =
-    {-# CORE "bar baz" #-}
+    {-# CORE "bar baz"#-}
     "hello"
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
@@ -4,8 +4,10 @@
   {-# SCC "barbaz" #-}
   "hello"
 
-corefoo = {-# CORE "foo" #-} 1
+-- CORE pragma got removed in https://gitlab.haskell.org/ghc/ghc/-/commit/12f9035200424ec8104484f154a040d612fee99d
 
+corefoo = {-# CORE "foo"#-} 1
+
 corebar =
-  {-# CORE "bar baz" #-}
+  {-# CORE "bar baz"#-}
   "hello"
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,8 @@
 sccbar = {-# SCC "barbaz"#-}
   "hello"
 
+-- CORE pragma got removed in https://gitlab.haskell.org/ghc/ghc/-/commit/12f9035200424ec8104484f154a040d612fee99d
+
 corefoo = {-# CORE "foo"#-}  1
 corebar = {-# CORE "bar baz"#-}
   "hello"
diff --git a/data/examples/declaration/value/function/record/constructors-four-out.hs b/data/examples/declaration/value/function/record/constructors-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/constructors-four-out.hs
@@ -0,0 +1,27 @@
+foo = Foo{a = 3}
+bar =
+    Bar
+        { abc = foo
+        , def = Foo{a = 10}
+        }
+baz = Baz{}
+sym = Foo{(+) = 3}
+aLongVariableName =
+    ALongRecordName
+        { short = baz
+        , aLongRecordFieldName =
+            YetAnotherLongRecordName
+                { yetAnotherLongRecordFieldName = "a long string"
+                }
+        , aLongRecordFieldName2 =
+            Just
+                YetAnotherLongRecordName
+                    { yetAnotherLongRecordFieldName = "a long string"
+                    , yetAnotherLongRecordFieldName =
+                        Just
+                            "a long string"
+                    }
+        , aLongRecordFieldName3 = do
+            foo
+            bar
+        }
diff --git a/data/examples/declaration/value/function/record/constructors-out.hs b/data/examples/declaration/value/function/record/constructors-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/constructors-out.hs
@@ -0,0 +1,31 @@
+foo = Foo {a = 3}
+
+bar =
+  Bar
+    { abc = foo,
+      def = Foo {a = 10}
+    }
+
+baz = Baz {}
+
+sym = Foo {(+) = 3}
+
+aLongVariableName =
+  ALongRecordName
+    { short = baz,
+      aLongRecordFieldName =
+        YetAnotherLongRecordName
+          { yetAnotherLongRecordFieldName = "a long string"
+          },
+      aLongRecordFieldName2 =
+        Just
+          YetAnotherLongRecordName
+            { yetAnotherLongRecordFieldName = "a long string",
+              yetAnotherLongRecordFieldName =
+                Just
+                  "a long string"
+            },
+      aLongRecordFieldName3 = do
+        foo
+        bar
+    }
diff --git a/data/examples/declaration/value/function/record/constructors.hs b/data/examples/declaration/value/function/record/constructors.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/constructors.hs
@@ -0,0 +1,22 @@
+foo = Foo { a = 3 }
+bar = Bar {
+    abc = foo,
+    def = Foo {a = 10}
+}
+baz = Baz{ }
+sym = Foo { (+) = 3 }
+aLongVariableName =
+  ALongRecordName
+    { short = baz,
+      aLongRecordFieldName = YetAnotherLongRecordName
+        { yetAnotherLongRecordFieldName = "a long string"
+          },
+      aLongRecordFieldName2 = Just YetAnotherLongRecordName
+                                 { yetAnotherLongRecordFieldName = "a long string",
+                                   yetAnotherLongRecordFieldName = Just
+                                                                     "a long string"
+                                   },
+      aLongRecordFieldName3 = do
+                               foo
+                               bar
+      }
diff --git a/data/examples/declaration/value/function/record/dot-multiline-four-out.hs b/data/examples/declaration/value/function/record/dot-multiline-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/dot-multiline-four-out.hs
@@ -0,0 +1,15 @@
+{-# 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-out.hs b/data/examples/declaration/value/function/record/dot-multiline-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/dot-multiline-out.hs
@@ -0,0 +1,15 @@
+{-# 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
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/dot-multiline.hs
@@ -0,0 +1,10 @@
+{-# 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-four-out.hs b/data/examples/declaration/value/function/record/dot-singleline-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/dot-singleline-four-out.hs
@@ -0,0 +1,15 @@
+{-# 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-out.hs b/data/examples/declaration/value/function/record/dot-singleline-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/dot-singleline-out.hs
@@ -0,0 +1,15 @@
+{-# 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
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/dot-singleline.hs
@@ -0,0 +1,14 @@
+{-# 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/hanging-after-equal-con-four-out.hs b/data/examples/declaration/value/function/record/hanging-after-equal-con-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/hanging-after-equal-con-four-out.hs
@@ -0,0 +1,8 @@
+foo =
+    Foo
+        { foo =
+            -- Blah blah
+            case x of
+                Blah -> 1
+                Blim -> 2
+        }
diff --git a/data/examples/declaration/value/function/record/hanging-after-equal-con-out.hs b/data/examples/declaration/value/function/record/hanging-after-equal-con-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/hanging-after-equal-con-out.hs
@@ -0,0 +1,8 @@
+foo =
+  Foo
+    { foo =
+        -- Blah blah
+        case x of
+          Blah -> 1
+          Blim -> 2
+    }
diff --git a/data/examples/declaration/value/function/record/hanging-after-equal-con.hs b/data/examples/declaration/value/function/record/hanging-after-equal-con.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/hanging-after-equal-con.hs
@@ -0,0 +1,7 @@
+foo = Foo
+  { foo =
+    -- Blah blah
+    case x of
+      Blah -> 1
+      Blim -> 2
+  }
diff --git a/data/examples/declaration/value/function/record/hanging-after-equal-update-four-out.hs b/data/examples/declaration/value/function/record/hanging-after-equal-update-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/hanging-after-equal-update-four-out.hs
@@ -0,0 +1,8 @@
+foo =
+    foo
+        { foo =
+            -- Blah blah
+            case x of
+                Blah -> 1
+                Blim -> 2
+        }
diff --git a/data/examples/declaration/value/function/record/hanging-after-equal-update-out.hs b/data/examples/declaration/value/function/record/hanging-after-equal-update-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/hanging-after-equal-update-out.hs
@@ -0,0 +1,8 @@
+foo =
+  foo
+    { foo =
+        -- Blah blah
+        case x of
+          Blah -> 1
+          Blim -> 2
+    }
diff --git a/data/examples/declaration/value/function/record/hanging-after-equal-update.hs b/data/examples/declaration/value/function/record/hanging-after-equal-update.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/hanging-after-equal-update.hs
@@ -0,0 +1,7 @@
+foo = foo
+  { foo =
+    -- Blah blah
+    case x of
+      Blah -> 1
+      Blim -> 2
+  }
diff --git a/data/examples/declaration/value/function/record/inter-comments-four-out.hs b/data/examples/declaration/value/function/record/inter-comments-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/inter-comments-four-out.hs
@@ -0,0 +1,13 @@
+x =
+    Just
+        -- comment
+        A
+            { x
+            }
+
+x =
+    Just
+        -- comment
+        a
+            { x
+            }
diff --git a/data/examples/declaration/value/function/record/inter-comments-out.hs b/data/examples/declaration/value/function/record/inter-comments-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/inter-comments-out.hs
@@ -0,0 +1,13 @@
+x =
+  Just
+    -- comment
+    A
+      { x
+      }
+
+x =
+  Just
+    -- comment
+    a
+      { x
+      }
diff --git a/data/examples/declaration/value/function/record/inter-comments.hs b/data/examples/declaration/value/function/record/inter-comments.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/inter-comments.hs
@@ -0,0 +1,11 @@
+x = Just
+  -- comment
+  A
+   { x
+   }
+
+x = Just
+  -- comment
+  a
+   { x
+   }
diff --git a/data/examples/declaration/value/function/record/updaters-four-out.hs b/data/examples/declaration/value/function/record/updaters-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/updaters-four-out.hs
@@ -0,0 +1,8 @@
+foo x = x{a = 3}
+bar x =
+    x
+        { abc = foo
+        , def = Foo{a = 10}
+        }
+baz x = x{}
+sym x = x{(+) = 4}
diff --git a/data/examples/declaration/value/function/record/updaters-out.hs b/data/examples/declaration/value/function/record/updaters-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/updaters-out.hs
@@ -0,0 +1,11 @@
+foo x = x {a = 3}
+
+bar x =
+  x
+    { abc = foo,
+      def = Foo {a = 10}
+    }
+
+baz x = x {}
+
+sym x = x {(+) = 4}
diff --git a/data/examples/declaration/value/function/record/updaters.hs b/data/examples/declaration/value/function/record/updaters.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/updaters.hs
@@ -0,0 +1,7 @@
+foo x = x { a = 3 }
+bar x = x {
+    abc = foo,
+    def = Foo {a = 10}
+}
+baz x = x{ }
+sym x = x { (+) = 4 }
diff --git a/data/examples/declaration/value/function/record/wildcards-four-out.hs b/data/examples/declaration/value/function/record/wildcards-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/wildcards-four-out.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RecordWildCards #-}
+
+foo x y = Foo{x, y}
+
+bar x y z =
+    Bar
+        { x
+        , y
+        , z
+        , ..
+        }
+
+baz = Baz{..}
diff --git a/data/examples/declaration/value/function/record/wildcards-out.hs b/data/examples/declaration/value/function/record/wildcards-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/wildcards-out.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RecordWildCards #-}
+
+foo x y = Foo {x, y}
+
+bar x y z =
+  Bar
+    { x,
+      y,
+      z,
+      ..
+    }
+
+baz = Baz {..}
diff --git a/data/examples/declaration/value/function/record/wildcards.hs b/data/examples/declaration/value/function/record/wildcards.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/declaration/value/function/record/wildcards.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RecordWildCards #-}
+
+foo x y = Foo { x, y }
+
+bar x y z = Bar {
+    x,
+    y,
+    z,
+    ..
+}
+
+baz = Baz { .. }
diff --git a/data/examples/module-header/multiline-with-comments-four-out.hs b/data/examples/module-header/multiline-with-comments-four-out.hs
--- a/data/examples/module-header/multiline-with-comments-four-out.hs
+++ b/data/examples/module-header/multiline-with-comments-four-out.hs
@@ -6,6 +6,9 @@
     -- * Something
     foo,
     bar,
+    -- | A multiline
+    -- comment here
+    bar2,
 
     -- * Another thing
     (<?>),
diff --git a/data/examples/module-header/multiline-with-comments-out.hs b/data/examples/module-header/multiline-with-comments-out.hs
--- a/data/examples/module-header/multiline-with-comments-out.hs
+++ b/data/examples/module-header/multiline-with-comments-out.hs
@@ -6,6 +6,9 @@
   ( -- * Something
     foo,
     bar,
+    -- | A multiline
+    -- comment here
+    bar2,
 
     -- * Another thing
     (<?>),
diff --git a/data/examples/module-header/multiline-with-comments.hs b/data/examples/module-header/multiline-with-comments.hs
--- a/data/examples/module-header/multiline-with-comments.hs
+++ b/data/examples/module-header/multiline-with-comments.hs
@@ -7,6 +7,9 @@
   ( -- * Something
     foo,
     bar,
+    -- | A multiline
+    -- comment here
+    bar2,
     -- * Another thing
     (<?>),
     {- some other thing -} foo2 -- yet another
diff --git a/data/examples/other/cpp/continuation-four-out.hs b/data/examples/other/cpp/continuation-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/continuation-four-out.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE CPP #-}
+
+foo :: Int
+foo = 5
+
+#define  message_for(a, b)  \
+   printf(#a " and " #b ": We love you!\n")
+
+bar :: Int
+bar = 6
diff --git a/data/examples/other/cpp/continuation-out.hs b/data/examples/other/cpp/continuation-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/continuation-out.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE CPP #-}
+
+foo :: Int
+foo = 5
+
+#define  message_for(a, b)  \
+   printf(#a " and " #b ": We love you!\n")
+
+bar :: Int
+bar = 6
diff --git a/data/examples/other/cpp/continuation.hs b/data/examples/other/cpp/continuation.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/continuation.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE CPP #-}
+
+foo :: Int
+foo = 5
+
+#define  message_for(a, b)  \
+   printf(#a " and " #b ": We love you!\n")
+
+bar :: Int
+bar = 6
diff --git a/data/examples/other/cpp/lonely-hash-four-out.hs b/data/examples/other/cpp/lonely-hash-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/lonely-hash-four-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Void
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+# if !MIN_VERSION_base(4,13,0)
+import Data.Semigroup ((<>))
+# endif
+
+main :: IO ()
+main = return ()
diff --git a/data/examples/other/cpp/lonely-hash-out.hs b/data/examples/other/cpp/lonely-hash-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/lonely-hash-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Void
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+# if !MIN_VERSION_base(4,13,0)
+import Data.Semigroup ((<>))
+# endif
+
+main :: IO ()
+main = return ()
diff --git a/data/examples/other/cpp/lonely-hash.hs b/data/examples/other/cpp/lonely-hash.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/lonely-hash.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Void
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+# if !MIN_VERSION_base(4,13,0)
+import Data.Semigroup ((<>))
+# endif
+
+main :: IO ()
+main = return ()
diff --git a/data/examples/other/cpp/separation-0a-four-out.hs b/data/examples/other/cpp/separation-0a-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-0a-four-out.hs
@@ -0,0 +1,8 @@
+{-# 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-out.hs b/data/examples/other/cpp/separation-0a-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-0a-out.hs
@@ -0,0 +1,8 @@
+{-# 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
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-0a.hs
@@ -0,0 +1,8 @@
+{-# 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-four-out.hs b/data/examples/other/cpp/separation-0b-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-0b-four-out.hs
@@ -0,0 +1,11 @@
+{-# 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
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-0b-out.hs b/data/examples/other/cpp/separation-0b-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-0b-out.hs
@@ -0,0 +1,11 @@
+{-# 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
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-0b.hs b/data/examples/other/cpp/separation-0b.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-0b.hs
@@ -0,0 +1,11 @@
+{-# 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
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-1a-four-out.hs b/data/examples/other/cpp/separation-1a-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-1a-four-out.hs
@@ -0,0 +1,13 @@
+{-# 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
+decompressingPipe BZip2   = throwM BZip2Unsupported
+#endif
diff --git a/data/examples/other/cpp/separation-1a-out.hs b/data/examples/other/cpp/separation-1a-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-1a-out.hs
@@ -0,0 +1,13 @@
+{-# 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
+decompressingPipe BZip2   = throwM BZip2Unsupported
+#endif
diff --git a/data/examples/other/cpp/separation-1a.hs b/data/examples/other/cpp/separation-1a.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-1a.hs
@@ -0,0 +1,13 @@
+{-# 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
+decompressingPipe BZip2   = throwM BZip2Unsupported
+#endif
diff --git a/data/examples/other/cpp/separation-1b-four-out.hs b/data/examples/other/cpp/separation-1b-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-1b-four-out.hs
@@ -0,0 +1,16 @@
+{-# 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
+decompressingPipe BZip2   = throwM BZip2Unsupported
+#endif
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-1b-out.hs b/data/examples/other/cpp/separation-1b-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-1b-out.hs
@@ -0,0 +1,16 @@
+{-# 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
+decompressingPipe BZip2   = throwM BZip2Unsupported
+#endif
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-1b.hs b/data/examples/other/cpp/separation-1b.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-1b.hs
@@ -0,0 +1,16 @@
+{-# 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
+decompressingPipe BZip2   = throwM BZip2Unsupported
+#endif
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-2a-four-out.hs b/data/examples/other/cpp/separation-2a-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-2a-four-out.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE CPP #-}
+
+ffff, ffffffff :: Natural
+#ifdef HASKELL_ZIP_DEV_MODE
+ffff     = 200
+ffffffff = 5000
+#else
+ffff     = 0xffff
+ffffffff = 0xffffffff
+#endif
diff --git a/data/examples/other/cpp/separation-2a-out.hs b/data/examples/other/cpp/separation-2a-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-2a-out.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE CPP #-}
+
+ffff, ffffffff :: Natural
+#ifdef HASKELL_ZIP_DEV_MODE
+ffff     = 200
+ffffffff = 5000
+#else
+ffff     = 0xffff
+ffffffff = 0xffffffff
+#endif
diff --git a/data/examples/other/cpp/separation-2a.hs b/data/examples/other/cpp/separation-2a.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-2a.hs
@@ -0,0 +1,10 @@
+{-# LANGUAGE CPP #-}
+
+ffff, ffffffff :: Natural
+#ifdef HASKELL_ZIP_DEV_MODE
+ffff     = 200
+ffffffff = 5000
+#else
+ffff     = 0xffff
+ffffffff = 0xffffffff
+#endif
diff --git a/data/examples/other/cpp/separation-2b-four-out.hs b/data/examples/other/cpp/separation-2b-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-2b-four-out.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE CPP #-}
+
+ffff, ffffffff :: Natural
+#ifdef HASKELL_ZIP_DEV_MODE
+ffff     = 200
+ffffffff = 5000
+#else
+ffff     = 0xffff
+ffffffff = 0xffffffff
+#endif
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-2b-out.hs b/data/examples/other/cpp/separation-2b-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-2b-out.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE CPP #-}
+
+ffff, ffffffff :: Natural
+#ifdef HASKELL_ZIP_DEV_MODE
+ffff     = 200
+ffffffff = 5000
+#else
+ffff     = 0xffff
+ffffffff = 0xffffffff
+#endif
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/separation-2b.hs b/data/examples/other/cpp/separation-2b.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/separation-2b.hs
@@ -0,0 +1,13 @@
+{-# LANGUAGE CPP #-}
+
+ffff, ffffffff :: Natural
+#ifdef HASKELL_ZIP_DEV_MODE
+ffff     = 200
+ffffffff = 5000
+#else
+ffff     = 0xffff
+ffffffff = 0xffffffff
+#endif
+
+foo :: Int
+foo = undefined
diff --git a/data/examples/other/cpp/shifted-four-out.hs b/data/examples/other/cpp/shifted-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/shifted-four-out.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE CPP #-}
+
+sinkData h compression = do
+    (uncompressedSize, crc32, compressedSize) <-
+        case compression of
+            Store ->
+                withCompression
+                    dataSink
+            Deflate ->
+                withCompression $
+                    Z.compress 9 (Z.WindowBits (-15)) .| dataSink
+#ifdef ENABLE_BZIP2
+      BZip2   -> withCompression $
+        BZ.bzip2 .| dataSink
+#else
+      BZip2   -> throwM BZip2Unsupported
+#endif
+  return
+      DataDescriptor
+          { ddCRC32 = fromIntegral crc32
+          , ddCompressedSize = compressedSize
+          , ddUncompressedSize = uncompressedSize
+          }
diff --git a/data/examples/other/cpp/shifted-out.hs b/data/examples/other/cpp/shifted-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/shifted-out.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE CPP #-}
+
+sinkData h compression = do
+  (uncompressedSize, crc32, compressedSize) <-
+    case compression of
+      Store ->
+        withCompression
+          dataSink
+      Deflate ->
+        withCompression $
+          Z.compress 9 (Z.WindowBits (-15)) .| dataSink
+#ifdef ENABLE_BZIP2
+      BZip2   -> withCompression $
+        BZ.bzip2 .| dataSink
+#else
+      BZip2   -> throwM BZip2Unsupported
+#endif
+  return
+    DataDescriptor
+      { ddCRC32 = fromIntegral crc32,
+        ddCompressedSize = compressedSize,
+        ddUncompressedSize = uncompressedSize
+      }
diff --git a/data/examples/other/cpp/shifted.hs b/data/examples/other/cpp/shifted.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/shifted.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE CPP #-}
+
+sinkData h compression = do
+  (uncompressedSize, crc32, compressedSize) <-
+    case compression of
+      Store   -> withCompression
+        dataSink
+      Deflate -> withCompression $
+        Z.compress 9 (Z.WindowBits (-15)) .| dataSink
+#ifdef ENABLE_BZIP2
+      BZip2   -> withCompression $
+        BZ.bzip2 .| dataSink
+#else
+      BZip2   -> throwM BZip2Unsupported
+#endif
+  return DataDescriptor
+    { ddCRC32            = fromIntegral crc32
+    , ddCompressedSize   = compressedSize
+    , ddUncompressedSize = uncompressedSize }
diff --git a/data/examples/other/cpp/simple-import-four-out.hs b/data/examples/other/cpp/simple-import-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/simple-import-four-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Void
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+#if !MIN_VERSION_base(4,13,0)
+import Data.Semigroup ((<>))
+#endif
+
+main :: IO ()
+main = return ()
diff --git a/data/examples/other/cpp/simple-import-out.hs b/data/examples/other/cpp/simple-import-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/simple-import-out.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Void
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+#if !MIN_VERSION_base(4,13,0)
+import Data.Semigroup ((<>))
+#endif
+
+main :: IO ()
+main = return ()
diff --git a/data/examples/other/cpp/simple-import.hs b/data/examples/other/cpp/simple-import.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/cpp/simple-import.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Data.Void
+import Test.Hspec
+import Test.Hspec.Megaparsec
+import Text.Megaparsec
+import Text.Megaparsec.Char
+
+#if !MIN_VERSION_base(4,13,0)
+import Data.Semigroup ((<>))
+#endif
+
+main :: IO ()
+main = return ()
diff --git a/data/examples/other/disabling/closing-comment-in-disabled-region-four-out.hs b/data/examples/other/disabling/closing-comment-in-disabled-region-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/closing-comment-in-disabled-region-four-out.hs
@@ -0,0 +1,3 @@
+{- ORMOLU_DISABLE -}
+a::String
+a="-}"
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-four-out.hs b/data/examples/other/disabling/empty-disabled-region-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/empty-disabled-region-four-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-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/line-pragma-in-disabled-region-four-out.hs b/data/examples/other/disabling/line-pragma-in-disabled-region-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/line-pragma-in-disabled-region-four-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-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/mixed-styles-four-out.hs b/data/examples/other/disabling/mixed-styles-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/mixed-styles-four-out.hs
@@ -0,0 +1,17 @@
+module Foo (foo, bar, baz, qux) where
+
+{- ORMOLU_DISABLE -}
+foo :: Char
+foo =  'a'
+{- FOURMOLU_ENABLE -}
+
+bar :: Char
+bar = 'b'
+
+{- FOURMOLU_DISABLE -}
+baz :: Char
+baz =  'c'
+{- ORMOLU_ENABLE -}
+
+qux :: Char
+qux = 'd'
diff --git a/data/examples/other/disabling/mixed-styles-out.hs b/data/examples/other/disabling/mixed-styles-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/mixed-styles-out.hs
@@ -0,0 +1,17 @@
+module Foo (foo, bar, baz, qux) where
+
+{- ORMOLU_DISABLE -}
+foo :: Char
+foo =  'a'
+{- FOURMOLU_ENABLE -}
+
+bar :: Char
+bar = 'b'
+
+{- FOURMOLU_DISABLE -}
+baz :: Char
+baz =  'c'
+{- ORMOLU_ENABLE -}
+
+qux :: Char
+qux = 'd'
diff --git a/data/examples/other/disabling/mixed-styles.hs b/data/examples/other/disabling/mixed-styles.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/mixed-styles.hs
@@ -0,0 +1,17 @@
+module Foo (foo, bar, baz, qux) where
+
+{- ORMOLU_DISABLE -}
+foo :: Char
+foo =  'a'
+{- FOURMOLU_ENABLE -}
+
+bar :: Char
+bar =  'b'
+
+{- FOURMOLU_DISABLE -}
+baz :: Char
+baz =  'c'
+{- ORMOLU_ENABLE -}
+
+qux :: Char
+qux =  'd'
diff --git a/data/examples/other/disabling/normalize-magic-comments-four-out.hs b/data/examples/other/disabling/normalize-magic-comments-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/normalize-magic-comments-four-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-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-four-out.hs b/data/examples/other/disabling/preserve-empty-lines-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/preserve-empty-lines-four-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-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-four-out.hs b/data/examples/other/disabling/preserve-trailing-whitespace-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/preserve-trailing-whitespace-four-out.hs
@@ -0,0 +1,5 @@
+foo = 2
+
+{- ORMOLU_DISABLE -}
+bar = 3  
+{- ORMOLU_ENABLE -}
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/single-definition-four-out.hs b/data/examples/other/disabling/single-definition-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/single-definition-four-out.hs
@@ -0,0 +1,10 @@
+module Foo (foo, bar) where
+
+{- ORMOLU_DISABLE -}
+foo :: Int -> Int
+foo = (+5)
+{- ORMOLU_ENABLE -}
+
+bar :: Bool -> Bool
+bar True = True
+bar False = True
diff --git a/data/examples/other/disabling/single-definition-out.hs b/data/examples/other/disabling/single-definition-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/single-definition-out.hs
@@ -0,0 +1,10 @@
+module Foo (foo, bar) where
+
+{- ORMOLU_DISABLE -}
+foo :: Int -> Int
+foo = (+5)
+{- ORMOLU_ENABLE -}
+
+bar :: Bool -> Bool
+bar True = True
+bar False = True
diff --git a/data/examples/other/disabling/single-definition.hs b/data/examples/other/disabling/single-definition.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/single-definition.hs
@@ -0,0 +1,10 @@
+module Foo (foo,bar) where
+
+{- ORMOLU_DISABLE -}
+foo :: Int -> Int
+foo = (+5)
+{- ORMOLU_ENABLE -}
+
+bar :: Bool -> Bool
+bar True  = True
+bar False = True
diff --git a/data/examples/other/disabling/whole-file-four-out.hs b/data/examples/other/disabling/whole-file-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/whole-file-four-out.hs
@@ -0,0 +1,10 @@
+{- ORMOLU_DISABLE -}
+
+module Foo (foo,bar) where
+
+foo :: Int -> Int
+foo = (+5)
+
+bar :: Bool -> Bool
+bar True  = True
+bar False = True
diff --git a/data/examples/other/disabling/whole-file-out.hs b/data/examples/other/disabling/whole-file-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/whole-file-out.hs
@@ -0,0 +1,10 @@
+{- ORMOLU_DISABLE -}
+
+module Foo (foo,bar) where
+
+foo :: Int -> Int
+foo = (+5)
+
+bar :: Bool -> Bool
+bar True  = True
+bar False = True
diff --git a/data/examples/other/disabling/whole-file.hs b/data/examples/other/disabling/whole-file.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/disabling/whole-file.hs
@@ -0,0 +1,10 @@
+{-    ORMOLU_DISABLE     -}
+
+module Foo (foo,bar) where
+
+foo :: Int -> Int
+foo = (+5)
+
+bar :: Bool -> Bool
+bar True  = True
+bar False = True
diff --git a/data/examples/other/haddock-without-module-header-four-out.hs b/data/examples/other/haddock-without-module-header-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/haddock-without-module-header-four-out.hs
@@ -0,0 +1,5 @@
+-- | Foo
+main = main
+
+-- | Bar
+data Bar
diff --git a/data/examples/other/haddock-without-module-header-out.hs b/data/examples/other/haddock-without-module-header-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/haddock-without-module-header-out.hs
@@ -0,0 +1,5 @@
+-- | Foo
+main = main
+
+-- | Bar
+data Bar
diff --git a/data/examples/other/haddock-without-module-header.hs b/data/examples/other/haddock-without-module-header.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/haddock-without-module-header.hs
@@ -0,0 +1,5 @@
+-- | Foo
+main =  main
+
+-- | Bar
+data Bar
diff --git a/data/examples/other/linear-arrows-four-out.hs b/data/examples/other/linear-arrows-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/linear-arrows-four-out.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+type a % b = (a, b)
+
+type Foo a m b = a % m -> b
+type Bar a m b = a %m -> b
+
+type Baz = a %1 -> b
+
+type M =
+    a %1 ->
+    b %m ->
+    c %1 ->
+    d
+
+test ::
+    a %1 ->
+    b %1 ->
+    c %m ->
+    d
+test = test
diff --git a/data/examples/other/linear-arrows-out.hs b/data/examples/other/linear-arrows-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/linear-arrows-out.hs
@@ -0,0 +1,23 @@
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+type a % b = (a, b)
+
+type Foo a m b = a % m -> b
+
+type Bar a m b = a %m -> b
+
+type Baz = a %1 -> b
+
+type M =
+  a %1 ->
+  b %m ->
+  c %1 ->
+  d
+
+test ::
+  a %1 ->
+  b %1 ->
+  c %m ->
+  d
+test = test
diff --git a/data/examples/other/linear-arrows.hs b/data/examples/other/linear-arrows.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/linear-arrows.hs
@@ -0,0 +1,20 @@
+{-# LANGUAGE LinearTypes #-}
+{-# LANGUAGE UnicodeSyntax #-}
+
+type a % b = (a,b)
+
+type Foo a m b = a % m -> b
+type Bar a m b = a %m -> b
+
+type Baz = a ⊸ b
+
+type M = a %1
+  -> b %m ->
+  c
+  %1 -> d
+
+test :: a
+      ⊸ b
+  %1 -> c
+  %m -> d
+test = test
diff --git a/data/examples/other/no-linear-arrows-four-out.hs b/data/examples/other/no-linear-arrows-four-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/no-linear-arrows-four-out.hs
@@ -0,0 +1,4 @@
+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-out.hs b/data/examples/other/no-linear-arrows-out.hs
new file mode 100644
--- /dev/null
+++ b/data/examples/other/no-linear-arrows-out.hs
@@ -0,0 +1,5 @@
+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
new file mode 100644
--- /dev/null
+++ b/data/examples/other/no-linear-arrows.hs
@@ -0,0 +1,4 @@
+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/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/fourmolu.cabal b/fourmolu.cabal
--- a/fourmolu.cabal
+++ b/fourmolu.cabal
@@ -1,56 +1,21 @@
-cabal-version:   1.18
-name:            fourmolu
-version:         0.3.0.0
-license:         BSD3
-license-file:    LICENSE.md
+cabal-version:      2.4
+name:               fourmolu
+version:            0.4.0.0
+license:            BSD-3-Clause
+license-file:       LICENSE.md
 maintainer:
     Matt Parsons <parsonsmatt@gmail.com>
     George Thomas <georgefsthomas@gmail.com>
-
-tested-with:     ghc ==8.6.5 ghc ==8.8.4 ghc ==8.10.1
-homepage:        https://github.com/parsonsmatt/fourmolu
-bug-reports:     https://github.com/parsonsmatt/fourmolu/issues
-synopsis:        A formatter for Haskell source code
-description:     A formatter for Haskell source code.
-category:        Development, Formatting
-build-type:      Simple
-data-files:
-    data/examples/declaration/annotation/*.hs
-    data/examples/declaration/class/*.hs
-    data/examples/declaration/data/*.hs
-    data/examples/declaration/data/gadt/*.hs
-    data/examples/declaration/default/*.hs
-    data/examples/declaration/deriving/*.hs
-    data/examples/declaration/foreign/*.hs
-    data/examples/declaration/instance/*.hs
-    data/examples/declaration/rewrite-rule/*.hs
-    data/examples/declaration/role-annotation/*.hs
-    data/examples/declaration/signature/complete/*.hs
-    data/examples/declaration/signature/fixity/*.hs
-    data/examples/declaration/signature/inline/*.hs
-    data/examples/declaration/signature/minimal/*.hs
-    data/examples/declaration/signature/pattern/*.hs
-    data/examples/declaration/signature/set-cost-centre/*.hs
-    data/examples/declaration/signature/specialize/*.hs
-    data/examples/declaration/signature/type/*.hs
-    data/examples/declaration/splice/*.hs
-    data/examples/declaration/type-families/closed-type-family/*.hs
-    data/examples/declaration/type-families/data-family/*.hs
-    data/examples/declaration/type-families/type-family/*.hs
-    data/examples/declaration/type-synonyms/*.hs
-    data/examples/declaration/type/*.hs
-    data/examples/declaration/value/function/*.hs
-    data/examples/declaration/value/function/arrow/*.hs
-    data/examples/declaration/value/function/comprehension/*.hs
-    data/examples/declaration/value/function/do/*.hs
-    data/examples/declaration/value/function/infix/*.hs
-    data/examples/declaration/value/function/pattern/*.hs
-    data/examples/declaration/value/other/*.hs
-    data/examples/declaration/value/pattern-synonyms/*.hs
-    data/examples/declaration/warning/*.hs
-    data/examples/import/*.hs
-    data/examples/module-header/*.hs
-    data/examples/other/*.hs
+tested-with:        ghc ==8.8.4 ghc ==8.10.7 ghc ==9.0.1
+homepage:           https://github.com/parsonsmatt/fourmolu
+bug-reports:        https://github.com/parsonsmatt/fourmolu/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
 
 extra-doc-files:
     CHANGELOG.md
@@ -69,7 +34,8 @@
     exposed-modules:
         Ormolu
         Ormolu.Config
-        Ormolu.Diff
+        Ormolu.Diff.ParseResult
+        Ormolu.Diff.Text
         Ormolu.Exception
         Ormolu.Imports
         Ormolu.Parser
@@ -77,7 +43,6 @@
         Ormolu.Parser.CommentStream
         Ormolu.Parser.Pragma
         Ormolu.Parser.Result
-        Ormolu.Parser.Shebang
         Ormolu.Printer
         Ormolu.Printer.Combinators
         Ormolu.Printer.Comments
@@ -106,31 +71,35 @@
         Ormolu.Printer.SpanStream
         Ormolu.Processing.Common
         Ormolu.Processing.Cpp
-        Ormolu.Processing.Postprocess
         Ormolu.Processing.Preprocess
+        Ormolu.Terminal
         Ormolu.Utils
+        Ormolu.Utils.Extensions
+        Ormolu.Utils.IO
 
     hs-source-dirs:   src
-    other-modules:
-        GHC
-        GHC.DynFlags
-
+    other-modules:    GHC.DynFlags
     default-language: Haskell2010
     build-depends:
-        aeson >=1.5.2 && <1.6,
+        Diff >=0.4 && <1.0,
+        aeson >=1.4.0.0 && <1.6,
+        ansi-terminal >=0.10 && <1.0,
         base >=4.12 && <5.0,
-        bytestring >=0.2 && <0.11,
+        bytestring >=0.2 && <0.12,
         containers >=0.5 && <0.7,
         directory >=1.3.3 && <1.4,
         dlist >=0.8 && <2.0,
         exceptions >=0.6 && <0.11,
         filepath >=1.4.2.1 && <1.5,
-        ghc-lib-parser >=8.10 && <8.11,
+        ghc-lib-parser >=9.0 && <9.1,
         HsYAML >=0.2 && <0.3,
         HsYAML-aeson >=0.2 && <0.3,
         mtl >=2.0 && <3.0,
         syb >=0.7 && <0.8,
-        text >=0.2 && <1.3
+        text >=0.2 && <1.3,
+        filepath >=1.2 && <1.5,
+        directory ^>=1.3,
+        Cabal ^>=3.4
 
     if flag(dev)
         ghc-options:
@@ -141,16 +110,21 @@
     else
         ghc-options: -O2 -Wall
 
+    if impl(ghc <8.10.0)
+        ghc-options: -fmax-pmcheck-iterations=3000000
+
 executable fourmolu
     main-is:          Main.hs
     hs-source-dirs:   app
     other-modules:    Paths_fourmolu
+    autogen-modules:  Paths_fourmolu
     default-language: Haskell2010
     build-depends:
-        fourmolu -any,
+        fourmolu,
         base >=4.12 && <5.0,
         directory >=1.3.3 && <1.4,
-        ghc-lib-parser >=8.10 && <8.11,
+        filepath >=1.2 && <1.5,
+        ghc-lib-parser >=9.0 && <9.1,
         gitrev >=1.3 && <1.4,
         optparse-applicative >=0.14 && <0.17,
         text >=0.2 && <1.3
@@ -165,23 +139,29 @@
         ghc-options: -O2 -Wall -rtsopts
 
 test-suite tests
-    type:             exitcode-stdio-1.0
-    main-is:          Spec.hs
-    build-tools:      hspec-discover >=2.0 && <3.0
-    hs-source-dirs:   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.CabalExtensionsSpec
+        Ormolu.Diff.TextSpec
+        Ormolu.Parser.OptionsSpec
+        Ormolu.Parser.ParseFailureSpec
         Ormolu.Parser.PragmaSpec
         Ormolu.PrinterSpec
 
-    default-language: Haskell2010
+    default-language:   Haskell2010
     build-depends:
+        fourmolu,
         base >=4.12 && <5.0,
         containers >=0.5 && <0.7,
+        directory ^>=1.3,
         filepath >=1.2 && <1.5,
         hspec >=2.0 && <3.0,
-        fourmolu -any,
-        path >=0.6 && <0.8,
+        path >=0.6 && <0.10,
         path-io >=1.4.2 && <2.0,
+        temporary ^>=1.3,
         text >=0.2 && <1.3
 
     if flag(dev)
diff --git a/src/GHC.hs b/src/GHC.hs
deleted file mode 100644
--- a/src/GHC.hs
+++ /dev/null
@@ -1,12 +0,0 @@
-module GHC
-  ( module X,
-  )
-where
-
-import ApiAnnotation as X
-import BasicTypes as X
-import GHC.Hs as X
-import GHC.Hs.Instances as X ()
-import Module as X
-import RdrName as X
-import SrcLoc as X
diff --git a/src/GHC/DynFlags.hs b/src/GHC/DynFlags.hs
--- a/src/GHC/DynFlags.hs
+++ b/src/GHC/DynFlags.hs
@@ -7,11 +7,11 @@
   )
 where
 
-import Config
-import DynFlags
-import Fingerprint
+import GHC.Driver.Session
 import GHC.Platform
-import ToolSettings
+import GHC.Settings
+import GHC.Settings.Config
+import GHC.Utils.Fingerprint
 
 fakeSettings :: Settings
 fakeSettings =
@@ -30,7 +30,14 @@
                 { platformMini_arch = ArchUnknown,
                   platformMini_os = OSUnknown
                 },
-            platformUnregisterised = True
+            platformUnregisterised = True,
+            platformByteOrder = LittleEndian,
+            platformHasGnuNonexecStack = False,
+            platformHasIdentDirective = False,
+            platformHasSubsectionsViaSymbols = False,
+            platformIsCrossCompiling = False,
+            platformLeadingUnderscore = False,
+            platformTablesNextToCode = False
           },
       sPlatformMisc = PlatformMisc {},
       sPlatformConstants =
diff --git a/src/Ormolu.hs b/src/Ormolu.hs
--- a/src/Ormolu.hs
+++ b/src/Ormolu.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
 
 -- | A formatter for Haskell source code.
 module Ormolu
@@ -6,6 +7,7 @@
     ormoluFile,
     ormoluStdin,
     Config (..),
+    ColorMode (..),
     RegionIndices (..),
     defaultConfig,
     DynOption (..),
@@ -22,21 +24,23 @@
   )
 where
 
-import qualified CmdLineParser as GHC
 import Control.Exception
 import Control.Monad
 import Control.Monad.IO.Class (MonadIO (..))
 import Data.Text (Text)
 import qualified Data.Text as T
 import Debug.Trace
+import qualified GHC.Driver.CmdLine as GHC
+import qualified GHC.Types.SrcLoc as GHC
 import Ormolu.Config
-import Ormolu.Diff
+import Ormolu.Diff.ParseResult
+import Ormolu.Diff.Text
 import Ormolu.Exception
 import Ormolu.Parser
 import Ormolu.Parser.Result
 import Ormolu.Printer
 import Ormolu.Utils (showOutputable)
-import qualified SrcLoc as GHC
+import Ormolu.Utils.IO
 
 -- | Format a 'String', return formatted version as 'Text'.
 --
@@ -65,35 +69,38 @@
   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 $ cfgPrinterOpts cfgWithIndices
   when (not (cfgUnsafe cfg) || cfgCheckIdempotence cfg) $ do
-    let pathRendered = path ++ "<rendered>"
     -- 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
         OrmoluOutputParsingFailed
-        pathRendered
+        path
         (T.unpack txt)
-    unless (cfgUnsafe cfg) $
-      case diffParseResult result0 result1 of
-        Same -> return ()
-        Different ss -> liftIO $ throwIO (OrmoluASTDiffers path ss)
+    unless (cfgUnsafe cfg) $ do
+      when (length result0 /= length result1) $
+        liftIO $ throwIO (OrmoluASTDiffers path [])
+      forM_ (result0 `zip` result1) $ \case
+        (ParsedSnippet s, ParsedSnippet s') -> case diffParseResult s s' of
+          Same -> return ()
+          Different ss -> liftIO $ throwIO (OrmoluASTDiffers path ss)
+        (RawSnippet {}, RawSnippet {}) -> pure ()
+        _ -> liftIO $ throwIO (OrmoluASTDiffers path [])
     -- Try re-formatting the formatted result to check if we get exactly
     -- the same output.
     when (cfgCheckIdempotence cfg) $
       let txt2 = printModule result1 $ cfgPrinterOpts cfgWithIndices
-       in case diffText txt txt2 pathRendered of
+       in case diffText txt txt2 path of
             Nothing -> return ()
-            Just (loc, l, r) ->
+            Just diff ->
               liftIO $
-                throwIO (OrmoluNonIdempotentOutput loc l r)
+                throwIO (OrmoluNonIdempotentOutput diff)
   return txt
 
 -- | Load a file and format it. The file stays intact and the rendered
@@ -110,7 +117,7 @@
   -- | Resulting rendition
   m Text
 ormoluFile cfg path =
-  liftIO (readFile path) >>= ormolu cfg path
+  readFileUtf8 path >>= ormolu cfg path . T.unpack
 
 -- | Read input from stdin and format it.
 --
@@ -139,7 +146,7 @@
   FilePath ->
   -- | Actual input for the parser
   String ->
-  m ([GHC.Warn], ParseResult)
+  m ([GHC.Warn], [SourceSnippet])
 parseModule' cfg mkException path str = do
   (warnings, r) <- parseModule cfg path str
   case r of
diff --git a/src/Ormolu/Config.hs b/src/Ormolu/Config.hs
--- a/src/Ormolu/Config.hs
+++ b/src/Ormolu/Config.hs
@@ -10,6 +10,7 @@
 -- | Configuration options used by the tool.
 module Ormolu.Config
   ( Config (..),
+    ColorMode (..),
     RegionIndices (..),
     RegionDeltas (..),
     defaultConfig,
@@ -43,7 +44,8 @@
 import Data.YAML (Pos)
 import Data.YAML.Aeson (decode1)
 import GHC.Generics (Generic)
-import qualified SrcLoc as GHC
+import qualified GHC.Types.SrcLoc as GHC
+import Ormolu.Terminal (ColorMode (..))
 import System.Directory
   ( XdgDirectory (XdgConfig),
     findFile,
@@ -62,6 +64,8 @@
     cfgDebug :: !Bool,
     -- | Checks if re-formatting the result is idempotent
     cfgCheckIdempotence :: !Bool,
+    -- | Whether to use colors and other features of ANSI terminals
+    cfgColorMode :: !ColorMode,
     -- | Region selection
     cfgRegion :: !region,
     cfgPrinterOpts :: !PrinterOptsTotal
@@ -95,6 +99,7 @@
       cfgUnsafe = False,
       cfgDebug = False,
       cfgCheckIdempotence = False,
+      cfgColorMode = Auto,
       cfgRegion =
         RegionIndices
           { regionStartLine = Nothing,
diff --git a/src/Ormolu/Diff.hs b/src/Ormolu/Diff.hs
deleted file mode 100644
--- a/src/Ormolu/Diff.hs
+++ /dev/null
@@ -1,158 +0,0 @@
-{-# LANGUAGE RankNTypes #-}
-
--- | Diffing GHC ASTs modulo span positions.
-module Ormolu.Diff
-  ( Diff (..),
-    diffParseResult,
-    diffText,
-  )
-where
-
-import Data.ByteString (ByteString)
-import Data.Generics
-import Data.Text (Text)
-import qualified Data.Text as T
-import qualified FastString as GHC
-import GHC
-import Ormolu.Imports (normalizeImports)
-import Ormolu.Parser.CommentStream
-import Ormolu.Parser.Result
-import Ormolu.Utils
-
--- | Result of comparing two 'ParseResult's.
-data Diff
-  = -- | Two parse results are the same
-    Same
-  | -- | Two parse results differ
-    Different [SrcSpan]
-
-instance Semigroup Diff where
-  Same <> a = a
-  a <> Same = a
-  Different xs <> Different ys = Different (xs ++ ys)
-
-instance Monoid Diff where
-  mempty = Same
-
--- | Return 'Diff' of two 'ParseResult's.
-diffParseResult :: ParseResult -> ParseResult -> Diff
-diffParseResult
-  ParseResult
-    { prCommentStream = cstream0,
-      prParsedSource = hs0
-    }
-  ParseResult
-    { prCommentStream = cstream1,
-      prParsedSource = hs1
-    } =
-    matchIgnoringSrcSpans cstream0 cstream1
-      <> matchIgnoringSrcSpans
-        hs0 {hsmodImports = concat . normalizeImports False $ hsmodImports hs0}
-        hs1 {hsmodImports = concat . normalizeImports False $ hsmodImports hs1}
-
--- | Compare two values for equality disregarding differences in 'SrcSpan's
--- and the ordering of import lists.
-matchIgnoringSrcSpans :: Data a => a -> a -> Diff
-matchIgnoringSrcSpans = genericQuery
-  where
-    genericQuery :: GenericQ (GenericQ Diff)
-    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 []
-      | typeOf x == typeOf y,
-        toConstr x == toConstr y =
-        mconcat $
-          gzipWithQ
-            ( genericQuery
-                `extQ` srcSpanEq
-                `extQ` commentEq
-                `extQ` sourceTextEq
-                `extQ` hsDocStringEq
-                `extQ` importDeclQualifiedStyleEq
-                `ext2Q` forLocated
-            )
-            x
-            y
-      | otherwise = Different []
-    srcSpanEq :: SrcSpan -> GenericQ Diff
-    srcSpanEq _ _ = Same
-    commentEq :: Comment -> GenericQ Diff
-    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 Diff
-    sourceTextEq _ _ = Same
-    importDeclQualifiedStyleEq :: ImportDeclQualifiedStyle -> GenericQ Diff
-    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 []
-    hsDocStringEq :: HsDocString -> GenericQ Diff
-    hsDocStringEq str0 str1' =
-      case cast str1' :: Maybe HsDocString of
-        Nothing -> Different []
-        Just str1 ->
-          if splitDocString str0 == splitDocString str1
-            then Same
-            else Different []
-    forLocated ::
-      (Data e0, Data e1) =>
-      GenLocated e0 e1 ->
-      GenericQ Diff
-    forLocated x@(L mspn _) y =
-      maybe id appendSpan (cast mspn) (genericQuery x y)
-    appendSpan :: SrcSpan -> Diff -> Diff
-    appendSpan s (Different ss) | fresh && helpful = Different (s : ss)
-      where
-        fresh = not $ any (`isSubspanOf` s) ss
-        helpful = isGoodSrcSpan s
-    appendSpan _ d = d
-
--- | Diff two texts and return the location they start to differ, alongside
--- with excerpts around that location.
-diffText ::
-  -- | Text before
-  Text ->
-  -- | Text after
-  Text ->
-  -- | Path to use to construct 'GHC.RealSrcLoc'
-  FilePath ->
-  Maybe (GHC.RealSrcLoc, Text, Text)
-diffText left right fp =
-  case go (0, 0, 0) left right of
-    Nothing -> Nothing
-    Just (row, col, loc) ->
-      Just
-        ( GHC.mkRealSrcLoc (GHC.mkFastString fp) row col,
-          getSpan loc left,
-          getSpan loc right
-        )
-  where
-    go (row, col, loc) t1 t2 =
-      case (T.uncons t1, T.uncons t2) of
-        -- both text empty, all good
-        (Nothing, Nothing) ->
-          Nothing
-        -- first chars are the same, adjust position and recurse
-        (Just (c1, r1), Just (c2, r2))
-          | c1 == c2 ->
-            let (row', col', loc') =
-                  if c1 == '\n'
-                    then (row + 1, 0, loc + 1)
-                    else (row, col + 1, loc + 1)
-             in go (row', col', loc') r1 r2
-        -- something is different, return the position
-        _ ->
-          Just (row, col, loc)
-    getSpan loc = T.take 20 . T.drop (loc - 10)
diff --git a/src/Ormolu/Diff/ParseResult.hs b/src/Ormolu/Diff/ParseResult.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Diff/ParseResult.hs
@@ -0,0 +1,149 @@
+{-# LANGUAGE BangPatterns #-}
+-- needed on GHC 9.0 due to simplified subsumption
+{-# LANGUAGE ImpredicativeTypes #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns #-}
+
+-- | This module allows us to diff two 'ParseResult's.
+module Ormolu.Diff.ParseResult
+  ( ParseResultDiff (..),
+    diffParseResult,
+  )
+where
+
+import Data.ByteString (ByteString)
+import Data.Generics
+import GHC.Hs
+import GHC.Types.Basic
+import GHC.Types.SrcLoc
+import Ormolu.Imports (normalizeImports)
+import Ormolu.Parser.CommentStream
+import Ormolu.Parser.Result
+import Ormolu.Utils
+
+-- | Result of comparing two 'ParseResult's.
+data ParseResultDiff
+  = -- | Two parse results are the same
+    Same
+  | -- | Two parse results differ
+    Different [SrcSpan]
+
+instance Semigroup ParseResultDiff where
+  Same <> a = a
+  a <> Same = a
+  Different xs <> Different ys = Different (xs ++ ys)
+
+instance Monoid ParseResultDiff where
+  mempty = Same
+
+-- | Return 'Diff' of two 'ParseResult's.
+diffParseResult ::
+  ParseResult ->
+  ParseResult ->
+  ParseResultDiff
+diffParseResult
+  ParseResult
+    { prCommentStream = cstream0,
+      prParsedSource = hs0
+    }
+  ParseResult
+    { prCommentStream = cstream1,
+      prParsedSource = hs1
+    } =
+    matchIgnoringSrcSpans cstream0 cstream1
+      <> matchIgnoringSrcSpans
+        hs0 {hsmodImports = concat . normalizeImports False $ hsmodImports hs0}
+        hs1 {hsmodImports = concat . normalizeImports False $ hsmodImports 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
+  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 []
+      | 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
+      | 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 ->
+      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 []
+    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 []
+    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)
+      where
+        fresh = not $ any (`isSubspanOf` s) ss
+        helpful = isGoodSrcSpan s
+    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 []
diff --git a/src/Ormolu/Diff/Text.hs b/src/Ormolu/Diff/Text.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Diff/Text.hs
@@ -0,0 +1,286 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | This module allows us to diff two 'Text' values.
+module Ormolu.Diff.Text
+  ( TextDiff,
+    diffText,
+    printTextDiff,
+  )
+where
+
+import Control.Monad
+import qualified Data.Algorithm.Diff as D
+import Data.Maybe (listToMaybe)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Ormolu.Terminal
+
+----------------------------------------------------------------------------
+-- Types
+
+-- | Result of diffing two 'Text's.
+data TextDiff = TextDiff FilePath DiffList
+  deriving (Eq)
+
+instance Show TextDiff where
+  show (TextDiff path _) = "TextDiff " ++ show path ++ " _"
+
+-- | List of lines tagged by 'D.Both', 'D.First', or 'D.Second'.
+type DiffList = [D.Diff [Text]]
+
+-- | Similar to 'DiffList', but with line numbers assigned.
+type DiffList' = [D.Diff [(Int, Int, Text)]]
+
+-- | Diff hunk.
+data Hunk = Hunk
+  { hunkFirstStartLine :: Int,
+    hunkFirstLength :: Int,
+    hunkSecondStartLine :: Int,
+    hunkSecondLength :: Int,
+    hunkDiff :: DiffList
+  }
+
+----------------------------------------------------------------------------
+-- API
+
+-- | Diff two texts and produce a 'TextDiff'.
+diffText ::
+  -- | Text before
+  Text ->
+  -- | Text after
+  Text ->
+  -- | Path to use
+  FilePath ->
+  -- | The resulting diff or 'Nothing' if the inputs are identical
+  Maybe TextDiff
+diffText a b path =
+  if all isBoth xs
+    then Nothing
+    else Just (TextDiff path xs)
+  where
+    xs = D.getGroupedDiff (lines' a) (lines' b)
+    isBoth = \case
+      D.Both _ _ -> True
+      D.First _ -> False
+      D.Second _ -> False
+    -- T.lines ignores trailing blank lines
+    lines' = T.splitOn "\n"
+
+-- | 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
+  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
+
+----------------------------------------------------------------------------
+-- Helpers
+
+-- | Assign lines to a 'DiffList'.
+assignLines :: DiffList -> DiffList'
+assignLines = go 1 1 id
+  where
+    go _ _ acc [] = acc []
+    go !firstLine !secondLine acc (x : xs) =
+      case x of
+        D.Both a b ->
+          let firstInc = length a
+              secondInc = length b
+              a' =
+                zipWith3
+                  (,,)
+                  (iterate (+ 1) firstLine)
+                  (iterate (+ 1) secondLine)
+                  a
+           in go
+                (firstLine + firstInc)
+                (secondLine + secondInc)
+                (acc . ((D.Both a' a') :))
+                xs
+        D.First a ->
+          let firstInc = length a
+              a' =
+                zipWith3
+                  (,,)
+                  (iterate (+ 1) firstLine)
+                  (repeat secondLine)
+                  a
+           in go
+                (firstLine + firstInc)
+                secondLine
+                (acc . ((D.First a') :))
+                xs
+        D.Second b ->
+          let secondInc = length b
+              b' =
+                zipWith3
+                  (,,)
+                  (repeat firstLine)
+                  (iterate (+ 1) secondLine)
+                  b
+           in go
+                firstLine
+                (secondLine + secondInc)
+                (acc . ((D.Second b') :))
+                xs
+
+-- | Form 'Hunk's from a 'DiffList''.
+toHunks :: DiffList' -> [Hunk]
+toHunks = go 0 False id id []
+  where
+    -- How many lines of context (that is, lines present in both texts) to
+    -- show per hunk.
+    margin = 3
+    go ::
+      Int ->
+      Bool ->
+      ([Hunk] -> [Hunk]) ->
+      (DiffList' -> DiffList') ->
+      [(Int, Int, Text)] ->
+      DiffList' ->
+      [Hunk]
+    go !n gotChanges hunksAcc currentAcc bothHistory = \case
+      [] ->
+        if gotChanges
+          then
+            let p = reverse (take margin bothHistory)
+                currentAcc' = addBothAfter p currentAcc
+             in case formHunk (currentAcc' []) of
+                  Nothing -> hunksAcc []
+                  Just hunk -> hunksAcc [hunk]
+          else hunksAcc []
+      (x : xs) ->
+        case x of
+          D.Both a _ ->
+            let currentAcc' = addBothAfter p currentAcc
+                p = reverse (drop (n' - margin) bothHistory')
+                hunksAcc' =
+                  case formHunk (currentAcc' []) of
+                    Nothing -> hunksAcc
+                    Just hunk -> hunksAcc . (hunk :)
+                bothHistory' = reverse a ++ bothHistory
+                lena = length a
+                n' = n + lena
+             in if gotChanges && n' > margin * 2
+                  then go 0 False hunksAcc' id bothHistory' xs
+                  else go n' gotChanges hunksAcc currentAcc bothHistory' xs
+          piece ->
+            if gotChanges
+              then
+                let p = reverse bothHistory
+                    currentAcc' = currentAcc . addBothBefore p (piece :)
+                 in go 0 True hunksAcc currentAcc' [] xs
+              else
+                let p = reverse (take margin bothHistory)
+                    currentAcc' = addBothBefore p (piece :)
+                 in go 0 True hunksAcc currentAcc' [] xs
+    addBothBefore [] acc = acc
+    addBothBefore p acc = (D.Both p p :) . acc
+    addBothAfter [] acc = acc
+    addBothAfter p acc = acc . (D.Both p p :)
+
+-- | Form a 'Hunk'.
+formHunk :: DiffList' -> Maybe Hunk
+formHunk xsRaw = do
+  let xs = trimEmpty xsRaw
+  hunkFirstStartLine <- listToMaybe xs >>= firstStartLine
+  let hunkFirstLength = firstLength xs
+  hunkSecondStartLine <- listToMaybe xs >>= secondStartLine
+  let hunkSecondLength = secondLength xs
+      hunkDiff = mapDiff (fmap third) xs
+  return Hunk {..}
+
+-- | Trim empty “both” lines from beginning and end of a 'DiffList''.
+trimEmpty :: DiffList' -> DiffList'
+trimEmpty = go True id
+  where
+    go isFirst acc = \case
+      [] -> acc []
+      [D.Both x _] ->
+        let x' = reverse $ dropWhile (T.null . third) (reverse x)
+         in go False (acc . (D.Both x' x' :)) []
+      (D.Both x _ : xs) ->
+        let x' = dropWhile (T.null . third) x
+         in if isFirst
+              then go False (acc . (D.Both x' x' :)) xs
+              else go False (acc . (D.Both x x :)) xs
+      (x : xs) ->
+        go False (acc . (x :)) xs
+
+firstStartLine :: D.Diff [(Int, Int, a)] -> Maybe Int
+firstStartLine = \case
+  D.Both ((x, _, _) : _) _ -> Just x
+  D.First ((x, _, _) : _) -> Just x
+  D.Second ((x, _, _) : _) -> Just x
+  _ -> Nothing
+
+firstLength :: [D.Diff [(Int, Int, a)]] -> Int
+firstLength = go 0
+  where
+    go n [] = n
+    go !n (x : xs) = case x of
+      D.Both as _ -> go (n + length as) xs
+      D.First as -> go (n + length as) xs
+      D.Second _ -> go n xs
+
+secondStartLine :: D.Diff [(Int, Int, a)] -> Maybe Int
+secondStartLine = \case
+  D.Both ((_, x, _) : _) _ -> Just x
+  D.First ((_, x, _) : _) -> Just x
+  D.Second ((_, x, _) : _) -> Just x
+  _ -> Nothing
+
+secondLength :: [D.Diff [(Int, Int, a)]] -> Int
+secondLength = go 0
+  where
+    go n [] = n
+    go !n (x : xs) = case x of
+      D.Both as _ -> go (n + length as) xs
+      D.First _ -> go n xs
+      D.Second as -> go (n + length as) xs
+
+mapDiff :: (a -> b) -> [D.Diff a] -> [D.Diff b]
+mapDiff f = fmap $ \case
+  D.Both a b -> D.Both (f a) (f b)
+  D.First a -> D.First (f a)
+  D.Second b -> D.Second (f b)
+
+third :: (Int, Int, Text) -> Text
+third (_, _, x) = x
diff --git a/src/Ormolu/Exception.hs b/src/Ormolu/Exception.hs
--- a/src/Ormolu/Exception.hs
+++ b/src/Ormolu/Exception.hs
@@ -1,4 +1,6 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
 
 -- | 'OrmoluException' type and surrounding definitions.
 module Ormolu.Exception
@@ -8,71 +10,105 @@
 where
 
 import Control.Exception
+import Control.Monad (forM_)
 import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.List.NonEmpty as NE
-import Data.Text (Text)
-import qualified GHC
-import Ormolu.Utils (showOutputable)
-import qualified Outputable as GHC
-import System.Exit (ExitCode (..), exitWith)
+import qualified Data.Text as T
+import GHC.Types.SrcLoc
+import Ormolu.Diff.Text (TextDiff, printTextDiff)
+import Ormolu.Terminal
+import System.Exit (ExitCode (..))
 import System.IO
 
 -- | Ormolu exception representing all cases when Ormolu can fail.
 data OrmoluException
   = -- | Parsing of original source code failed
-    OrmoluParsingFailed GHC.SrcSpan String
+    OrmoluParsingFailed SrcSpan String
   | -- | Parsing of formatted source code failed
-    OrmoluOutputParsingFailed GHC.SrcSpan String
+    OrmoluOutputParsingFailed SrcSpan String
   | -- | Original and resulting ASTs differ
-    OrmoluASTDiffers FilePath [GHC.SrcSpan]
+    OrmoluASTDiffers FilePath [SrcSpan]
   | -- | Formatted source code is not idempotent
-    OrmoluNonIdempotentOutput GHC.RealSrcLoc Text Text
+    OrmoluNonIdempotentOutput TextDiff
   | -- | Some GHC options were not recognized
     OrmoluUnrecognizedOpts (NonEmpty String)
+  | -- | Cabal file parsing failed
+    OrmoluCabalFileParsingFailed FilePath
+  | -- | Missing input file path when using stdin input and
+    -- accounting for .cabal files
+    OrmoluMissingStdinInputFile
   deriving (Eq, Show)
 
-instance Exception OrmoluException where
-  displayException = \case
-    OrmoluParsingFailed s e ->
-      showParsingErr "The GHC parser (in Haddock mode) failed:" s [e]
-    OrmoluOutputParsingFailed s e ->
-      showParsingErr "Parsing of formatted code failed:" s [e]
-        ++ "Please, consider reporting the bug.\n"
-    OrmoluASTDiffers path ss ->
-      unlines $
-        [ "AST of input and AST of formatted code differ."
-        ]
-          ++ fmap
-            withIndent
-            ( case fmap (\s -> "at " ++ showOutputable s) ss of
-                [] -> ["in " ++ path]
-                xs -> xs
-            )
-          ++ ["Please, consider reporting the bug."]
-    OrmoluNonIdempotentOutput loc left right ->
-      showParsingErr
-        "Formatting is not idempotent:"
-        loc
-        ["before: " ++ show left, "after:  " ++ show right]
-        ++ "Please, consider reporting the bug.\n"
-    OrmoluUnrecognizedOpts opts ->
-      unlines
-        [ "The following GHC options were not recognized:",
-          (withIndent . unwords . NE.toList) opts
-        ]
+instance Exception OrmoluException
 
+-- | Print an 'OrmoluException'.
+printOrmoluException ::
+  OrmoluException ->
+  Term ()
+printOrmoluException = \case
+  OrmoluParsingFailed s e -> do
+    bold (putSrcSpan s)
+    newline
+    put "  The GHC parser (in Haddock mode) failed:"
+    newline
+    put "  "
+    put (T.pack e)
+    newline
+  OrmoluOutputParsingFailed s e -> do
+    bold (putSrcSpan s)
+    newline
+    put "  Parsing of formatted code failed:"
+    put "  "
+    put (T.pack e)
+    newline
+  OrmoluASTDiffers path ss -> do
+    putS path
+    newline
+    put "  AST of input and AST of formatted code differ."
+    newline
+    forM_ ss $ \s -> do
+      put "    at "
+      putSrcSpan s
+      newline
+    put "  Please, consider reporting the bug."
+    newline
+  OrmoluNonIdempotentOutput diff -> do
+    printTextDiff diff
+    newline
+    put "  Formatting is not idempotent."
+    newline
+    put "  Please, consider reporting the bug."
+    newline
+  OrmoluUnrecognizedOpts opts -> do
+    put "The following GHC options were not recognized:"
+    newline
+    put "  "
+    (putS . unwords . NE.toList) opts
+    newline
+  OrmoluCabalFileParsingFailed cabalFile -> do
+    put "Parsing this .cabal file failed:"
+    newline
+    put $ "  " <> T.pack cabalFile
+    newline
+  OrmoluMissingStdinInputFile -> do
+    put "The --stdin-input-file option is necessary when using input"
+    newline
+    put "from stdin and accounting for .cabal files"
+    newline
+
 -- | Inside this wrapper 'OrmoluException' will be caught and displayed
--- nicely using 'displayException'.
+-- nicely.
 withPrettyOrmoluExceptions ::
-  -- | Action that may throw the exception
-  IO a ->
-  IO a
-withPrettyOrmoluExceptions m = m `catch` h
+  -- | Color mode
+  ColorMode ->
+  -- | Action that may throw an exception
+  IO ExitCode ->
+  IO ExitCode
+withPrettyOrmoluExceptions colorMode m = m `catch` h
   where
-    h :: OrmoluException -> IO a
     h e = do
-      hPutStrLn stderr (displayException e)
-      exitWith . ExitFailure $
+      runTerm (printOrmoluException e) colorMode stderr
+      return . ExitFailure $
         case e of
           -- Error code 1 is for 'error' or 'notImplemented'
           -- 2 used to be for erroring out on CPP
@@ -81,19 +117,5 @@
           OrmoluASTDiffers {} -> 5
           OrmoluNonIdempotentOutput {} -> 6
           OrmoluUnrecognizedOpts {} -> 7
-
-----------------------------------------------------------------------------
--- Helpers
-
--- | Show a parse error.
-showParsingErr :: GHC.Outputable a => String -> a -> [String] -> String
-showParsingErr msg spn err =
-  unlines $
-    [ msg,
-      withIndent (showOutputable spn)
-    ]
-      ++ map withIndent err
-
--- | Indent with 2 spaces for readability.
-withIndent :: String -> String
-withIndent txt = "  " ++ txt
+          OrmoluCabalFileParsingFailed {} -> 8
+          OrmoluMissingStdinInputFile {} -> 9
diff --git a/src/Ormolu/Imports.hs b/src/Ormolu/Imports.hs
--- a/src/Ormolu/Imports.hs
+++ b/src/Ormolu/Imports.hs
@@ -15,10 +15,14 @@
 import Data.List (foldl', nubBy, sortBy, sortOn)
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as M
-import FastString (FastString)
-import GHC hiding (GhcPs, IE)
+import GHC.Data.FastString (FastString)
 import GHC.Hs.Extension
-import GHC.Hs.ImpExp (IE (..))
+import GHC.Hs.ImpExp as GHC
+import GHC.Types.Basic
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
+import GHC.Unit.Module.Name
+import GHC.Unit.Types
 import Ormolu.Utils (groupBy', notImplemented, separatedByBlank, showOutputable)
 
 -- | Sort, group and normalize imports. Assumes input list is sorted by source location.
@@ -41,7 +45,6 @@
           { ideclHiding = second (fmap normalizeLies) <$> ideclHiding,
             ..
           }
-    g _ = notImplemented "XImportDecl"
 
 -- | Combine two import declarations. It should be assumed that 'ImportId's
 -- are equal.
@@ -59,7 +62,6 @@
           _ -> Nothing,
         ..
       }
-combineImports _ _ = notImplemented "XImportDecl"
 
 -- | Import id, a collection of all things that justify having a separate
 -- import entry. This is used for merging of imports. If two imports have
@@ -68,7 +70,7 @@
   { importIsPrelude :: Bool,
     importIdName :: ModuleName,
     importPkgQual :: Maybe FastString,
-    importSource :: Bool,
+    importSource :: IsBootInterface,
     importSafe :: Bool,
     importQualified :: Bool,
     importImplicit :: Bool,
@@ -97,7 +99,6 @@
   where
     isPrelude = moduleNameString moduleName == "Prelude"
     moduleName = unLoc ideclName
-importId _ = notImplemented "XImportDecl"
 
 -- | Normalize a collection of import\/export items.
 normalizeLies :: [LIE GhcPs] -> [LIE GhcPs]
@@ -146,12 +147,10 @@
                         IEGroup NoExtField _ _ -> notImplemented "IEGroup"
                         IEDoc NoExtField _ -> notImplemented "IEDoc"
                         IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
-                        XIE x -> noExtCon x
                     IEModuleContents NoExtField _ -> notImplemented "IEModuleContents"
                     IEGroup NoExtField _ _ -> notImplemented "IEGroup"
                     IEDoc NoExtField _ -> notImplemented "IEDoc"
                     IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
-                    XIE x -> noExtCon x
                in Just (f <$> old)
        in M.alter alter wname m
 
@@ -174,7 +173,6 @@
   IEGroup NoExtField _ _ -> notImplemented "IEGroup"
   IEDoc NoExtField _ -> notImplemented "IEDoc"
   IEDocNamed NoExtField _ -> notImplemented "IEDocNamed"
-  XIE x -> noExtCon x
 
 -- | Like 'compareIewn' for located wrapped names.
 compareLIewn :: LIEWrappedName RdrName -> LIEWrappedName RdrName -> Ordering
diff --git a/src/Ormolu/Parser.hs b/src/Ormolu/Parser.hs
--- a/src/Ormolu/Parser.hs
+++ b/src/Ormolu/Parser.hs
@@ -1,4 +1,5 @@
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
@@ -9,33 +10,36 @@
   )
 where
 
-import Bag (bagToList)
-import qualified CmdLineParser as GHC
 import Control.Exception
-import Control.Monad.IO.Class
+import Control.Monad.Except
+import Data.Functor
 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 DynFlags as GHC
-import ErrUtils (Severity (..), errMsgSeverity, errMsgSpan)
-import qualified FastString as GHC
-import GHC hiding (IE, UnicodeSyntax)
+import GHC.Data.Bag (bagToList)
+import qualified GHC.Data.EnumSet as EnumSet
+import qualified GHC.Data.FastString as GHC
+import qualified GHC.Data.StringBuffer as GHC
+import qualified GHC.Driver.CmdLine as GHC
+import GHC.Driver.Session as GHC
+import qualified GHC.Driver.Types as GHC
 import GHC.DynFlags (baseDynFlags)
 import GHC.LanguageExtensions.Type (Extension (..))
-import qualified HeaderInfo as GHC
-import qualified HscTypes as GHC
-import qualified Lexer as GHC
+import qualified GHC.Parser as GHC
+import qualified GHC.Parser.Header as GHC
+import qualified GHC.Parser.Lexer 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 Ormolu.Config
 import Ormolu.Exception
 import Ormolu.Parser.Anns
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Result
-import Ormolu.Processing.Preprocess (preprocess)
-import Ormolu.Utils (incSpanLine, removeIndentation)
-import qualified Panic as GHC
-import qualified Parser as GHC
-import qualified StringBuffer as GHC
+import Ormolu.Processing.Common
+import Ormolu.Processing.Preprocess
+import Ormolu.Utils (incSpanLine)
 
 -- | Parse a complete module from string.
 parseModule ::
@@ -48,12 +52,9 @@
   String ->
   m
     ( [GHC.Warn],
-      Either (SrcSpan, String) ParseResult
+      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 {..} 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.
@@ -71,6 +72,23 @@
                 (mkSrcLoc (GHC.mkFastString path) 1 1)
                 (mkSrcLoc (GHC.mkFastString path) 1 1)
          in throwIO (OrmoluParsingFailed loc err)
+  let cppEnabled = EnumSet.member Cpp (GHC.extensionFlags dynFlags)
+  snippets <- runExceptT . forM (preprocess cppEnabled cfgRegion rawInput) $ \case
+    Right region ->
+      fmap ParsedSnippet . ExceptT $
+        parseModuleSnippet (config $> region) dynFlags path rawInput
+    Left raw -> pure $ RawSnippet raw
+  pure (warnings, snippets)
+
+parseModuleSnippet ::
+  MonadIO m =>
+  Config RegionDeltas ->
+  DynFlags ->
+  FilePath ->
+  String ->
+  m (Either (SrcSpan, String) ParseResult)
+parseModuleSnippet Config {..} dynFlags path rawInput = liftIO $ do
+  let (input, indent) = removeIndentation . linesInRegion cfgRegion $ rawInput
   let useRecordDot =
         "record-dot-preprocessor" == pgm_F dynFlags
           || any
@@ -97,24 +115,20 @@
             -- 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 pstate
                in Right
                     ParseResult
                       { prParsedSource = hsModule,
                         prAnns = mkAnns pstate,
                         prStackHeader = stackHeader,
-                        prShebangs = shebangs,
                         prPragmas = pragmas,
                         prCommentStream = comments,
                         prUseRecordDot = useRecordDot,
-                        prImportQualifiedPost =
-                          GHC.xopt ImportQualifiedPost dynFlags,
-                        prLiteralPrefix = T.pack literalPrefix,
-                        prLiteralSuffix = T.pack literalSuffix,
+                        prExtensions = GHC.extensionFlags dynFlags,
                         prIndent = indent
                       }
-  return (warnings, r)
+  return r
 
 -- | Enable all language extensions that we think should be enabled by
 -- default for ease of use.
@@ -137,17 +151,20 @@
     TransformListComp, -- steals the group keyword
     UnboxedTuples, -- breaks (#) lens operator
     MagicHash, -- screws {-# these things #-}
-    TypeApplications, -- steals (@) operator on some cases
     AlternativeLayoutRule,
     AlternativeLayoutRuleTransitional,
     MonadComprehensions,
     UnboxedSums,
     UnicodeSyntax, -- gives special meanings to operators like (→)
+    TemplateHaskell, -- changes how $foo is parsed
     TemplateHaskellQuotes, -- enables TH subset of quasi-quotes, this
     -- apparently interferes with QuasiQuotes in
     -- weird ways
-    ImportQualifiedPost -- affects how Ormolu renders imports, so the
+    ImportQualifiedPost, -- affects how Ormolu renders imports, so the
     -- 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
   ]
 
 -- | Run a 'GHC.P' computation.
@@ -164,7 +181,7 @@
   GHC.ParseResult a
 runParser parser flags filename input = GHC.unP parser parseState
   where
-    location = GHC.mkRealSrcLoc (GHC.mkFastString filename) 1 1
+    location = mkRealSrcLoc (GHC.mkFastString filename) 1 1
     buffer = GHC.stringToStringBuffer input
     parseState = GHC.mkPState flags buffer location
 
@@ -202,9 +219,9 @@
   IO (Either String ([GHC.Warn], DynFlags))
 parsePragmasIntoDynFlags flags extraOpts filepath str =
   catchErrors $ do
-    let opts = GHC.getOptions flags (GHC.stringToStringBuffer str) filepath
+    let fileOpts = GHC.getOptions flags (GHC.stringToStringBuffer str) filepath
     (flags', leftovers, warnings) <-
-      parseDynamicFilePragma flags (opts <> extraOpts)
+      parseDynamicFilePragma flags (extraOpts <> fileOpts)
     case NE.nonEmpty leftovers of
       Nothing -> return ()
       Just unrecognizedOpts ->
diff --git a/src/Ormolu/Parser/Anns.hs b/src/Ormolu/Parser/Anns.hs
--- a/src/Ormolu/Parser/Anns.hs
+++ b/src/Ormolu/Parser/Anns.hs
@@ -9,38 +9,34 @@
 
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as M
-import Data.Maybe (mapMaybe)
-import qualified GHC
-import qualified Lexer as GHC
-import SrcLoc
+import GHC.Parser.Annotation
+import GHC.Parser.Lexer
+import GHC.Types.SrcLoc
 
 -- | Ormolu-specific representation of GHC annotations.
-newtype Anns = Anns (Map RealSrcSpan [GHC.AnnKeywordId])
+newtype Anns = Anns (Map RealSrcSpan [AnnKeywordId])
   deriving (Eq)
 
 -- | Empty 'Anns'.
 emptyAnns :: Anns
 emptyAnns = Anns M.empty
 
--- | Create 'Anns' from 'GHC.PState'.
+-- | Create 'Anns' from 'PState'.
 mkAnns ::
-  GHC.PState ->
+  PState ->
   Anns
 mkAnns pstate =
   Anns $
-    M.fromListWith (++) (mapMaybe f (GHC.annotations pstate))
+    M.fromListWith (++) (f <$> annotations pstate)
   where
-    f ((spn, kid), _) =
-      case spn of
-        RealSrcSpan rspn -> Just (rspn, [kid])
-        UnhelpfulSpan _ -> Nothing
+    f ((rspn, kid), _) = (rspn, [kid])
 
--- | Lookup 'GHC.AnnKeywordId's corresponding to a given 'SrcSpan'.
+-- | Lookup 'AnnKeywordId's corresponding to a given 'SrcSpan'.
 lookupAnns ::
   -- | Span to lookup with
   SrcSpan ->
   -- | Collection of annotations
   Anns ->
-  [GHC.AnnKeywordId]
-lookupAnns (RealSrcSpan rspn) (Anns m) = M.findWithDefault [] rspn m
+  [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
@@ -23,13 +23,11 @@
 import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe (mapMaybe)
-import qualified GHC
-import qualified Lexer as GHC
+import qualified GHC.Parser.Annotation as GHC
+import qualified GHC.Parser.Lexer as GHC
+import GHC.Types.SrcLoc
 import Ormolu.Parser.Pragma
-import Ormolu.Parser.Shebang
-import Ormolu.Processing.Common
 import Ormolu.Utils (onTheSameLine, showOutputable)
-import SrcLoc
 
 ----------------------------------------------------------------------------
 -- Comment stream
@@ -39,25 +37,20 @@
 newtype CommentStream = CommentStream [RealLocated Comment]
   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 'GHC.PState'. The pragmas are
+-- removed from the 'CommentStream'.
 mkCommentStream ::
   -- | Original input
   String ->
-  -- | Extra comments to include
-  [Located String] ->
   -- | Parser state to use for comment extraction
   GHC.PState ->
-  -- | Stack header, shebangs, pragmas, and comment stream
+  -- | Stack header, pragmas, and comment stream
   ( Maybe (RealLocated Comment),
-    [Shebang],
     [([RealLocated Comment], Pragma)],
     CommentStream
   )
-mkCommentStream input extraComments pstate =
+mkCommentStream input pstate =
   ( mstackHeader,
-    shebangs,
     pragmas,
     CommentStream comments
   )
@@ -65,13 +58,11 @@
     (comments, pragmas) = extractPragmas input rawComments1
     (rawComments1, mstackHeader) = extractStackHeader rawComments0
     rawComments0 =
-      L.sortOn (realSrcSpanStart . getRealSrcSpan) . mapMaybe toRealSpan $
-        otherExtraComments
-          ++ mapMaybe (liftMaybe . fmap unAnnotationComment) (GHC.comment_q pstate)
+      L.sortOn (realSrcSpanStart . getRealSrcSpan) $
+        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
@@ -79,7 +70,7 @@
   unlines $
     showComment <$> xs
   where
-    showComment (GHC.L l str) = showOutputable l ++ " " ++ show str
+    showComment (L l str) = showOutputable l ++ " " ++ show str
 
 ----------------------------------------------------------------------------
 -- Comment
@@ -110,15 +101,11 @@
             Nothing -> s :| []
             Just (x :| xs) ->
               let getIndent y =
-                    if all isSpace y || y == endDisabling
+                    if all isSpace y
                       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)
+               in x :| (drop n <$> xs)
           else s :| []
     (atomsBefore, ls') =
       case dropWhile ((< commentLine) . fst) ls of
@@ -189,8 +176,8 @@
                   (y : ys) ->
                     let (ls', y') = mkComment ls y
                      in if onTheSameLine
-                          (RealSrcSpan (getRealSrcSpan x))
-                          (RealSrcSpan (getRealSrcSpan y))
+                          (RealSrcSpan (getRealSrcSpan x) Nothing)
+                          (RealSrcSpan (getRealSrcSpan y) Nothing)
                           then go' ls' [y'] ys
                           else go' ls [] xs
 
@@ -205,14 +192,10 @@
   GHC.AnnLineComment s -> Just s
   GHC.AnnBlockComment s -> Just s
 
-liftMaybe :: Located (Maybe a) -> Maybe (Located a)
+liftMaybe :: GenLocated l (Maybe a) -> Maybe (GenLocated l a)
 liftMaybe = \case
   L _ Nothing -> Nothing
   L l (Just a) -> Just (L l a)
-
-toRealSpan :: Located a -> Maybe (RealLocated a)
-toRealSpan (L (RealSrcSpan l) a) = Just (L l a)
-toRealSpan _ = Nothing
 
 -- | Remove consecutive blank lines.
 removeConseqBlanks :: NonEmpty String -> NonEmpty 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
@@ -11,12 +11,12 @@
 import Control.Monad
 import Data.Char (isSpace, toLower)
 import qualified Data.List as L
-import qualified EnumSet as ES
-import FastString (mkFastString, unpackFS)
-import qualified Lexer as L
-import Module (ComponentId (..), newSimpleUnitId)
-import SrcLoc
-import StringBuffer
+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 GHC.Types.SrcLoc
+import GHC.Unit.Module (stringToUnitId)
 
 -- | Ormolu's representation of pragmas.
 data Pragma
@@ -72,7 +72,7 @@
       L.mkParserFlags'
         ES.empty
         ES.empty
-        (newSimpleUnitId (ComponentId (mkFastString "")))
+        (stringToUnitId "")
         True
         True
         True
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
@@ -2,50 +2,39 @@
 
 -- | A type for result of parsing.
 module Ormolu.Parser.Result
-  ( ParseResult (..),
-    prettyPrintParseResult,
+  ( SourceSnippet (..),
+    ParseResult (..),
   )
 where
 
 import Data.Text (Text)
-import GHC
+import GHC.Data.EnumSet (EnumSet)
+import GHC.Hs
+import GHC.LanguageExtensions.Type
+import GHC.Types.SrcLoc
 import Ormolu.Parser.Anns
 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 GhcPs,
+    prParsedSource :: HsModule,
     -- | Ormolu-specfic representation of annotations
     prAnns :: Anns,
     -- | Stack header
     prStackHeader :: Maybe (RealLocated Comment),
-    -- | Shebangs found in the input
-    prShebangs :: [Shebang],
     -- | Pragmas and the associated comments
     prPragmas :: [([RealLocated Comment], Pragma)],
     -- | Comment stream
     prCommentStream :: CommentStream,
     -- | Whether or not record dot syntax is enabled
     prUseRecordDot :: Bool,
-    -- | Whether or not ImportQualifiedPost is enabled
-    prImportQualifiedPost :: Bool,
-    -- | Literal prefix
-    prLiteralPrefix :: Text,
-    -- | Literal suffix
-    prLiteralSuffix :: Text,
+    -- | Enabled extensions
+    prExtensions :: EnumSet Extension,
     -- | 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 SrcLoc
-
--- | A wrapper for a shebang.
-newtype Shebang = Shebang (Located String)
-  deriving (Eq, Data)
-
--- | Extract shebangs from the beginning of a comment stream.
-extractShebangs :: [Located String] -> ([Shebang], [Located 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,3 +1,5 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Pretty-printer for Haskell AST.
@@ -8,35 +10,36 @@
 where
 
 import Data.Text (Text)
+import qualified Data.Text as T
 import Ormolu.Config
 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 ::
   -- | Result of parsing
-  ParseResult ->
+  [SourceSnippet] ->
   PrinterOptsTotal ->
   -- | Resulting rendition
   Text
-printModule ParseResult {..} printerOpts =
-  prLiteralPrefix <> region <> prLiteralSuffix
+printModule snippets printerOpts = T.concat . fmap printSnippet $ snippets
   where
-    region =
-      postprocess prIndent $
-        runR
-          ( p_hsModule
-              prStackHeader
-              prShebangs
-              prPragmas
-              prImportQualifiedPost
-              prParsedSource
-          )
-          (mkSpanStream prParsedSource)
-          prCommentStream
-          prAnns
-          printerOpts
-          prUseRecordDot
+    printSnippet = \case
+      ParsedSnippet ParseResult {..} ->
+        reindent prIndent $
+          runR
+            ( p_hsModule
+                prStackHeader
+                prPragmas
+                prParsedSource
+            )
+            (mkSpanStream prParsedSource)
+            prCommentStream
+            prAnns
+            printerOpts
+            prUseRecordDot
+            prExtensions
+      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
@@ -12,6 +12,7 @@
     runR,
     getAnns,
     getEnclosingSpan,
+    isExtensionEnabled,
 
     -- * Combinators
 
@@ -23,9 +24,11 @@
     declNewline,
     inci,
     inciIf,
-    inciBy,
+    inciByFrac,
+    inciHalf,
     located,
     located',
+    realLocated,
     switchLayout,
     Layout (..),
     vlayout,
@@ -47,6 +50,7 @@
     backticks,
     banana,
     braces,
+    recordDotBraces,
     brackets,
     parens,
     parensHash,
@@ -70,10 +74,10 @@
 import Control.Monad
 import Data.List (intersperse)
 import Data.Text (Text)
+import GHC.Types.SrcLoc
 import Ormolu.Config
 import Ormolu.Printer.Comments
 import Ormolu.Printer.Internal
-import SrcLoc
 
 ----------------------------------------------------------------------------
 -- Basic
@@ -99,10 +103,19 @@
   (a -> R ()) ->
   R ()
 located (L (UnhelpfulSpan _) a) f = f a
-located (L (RealSrcSpan l) a) f = do
+located (L (RealSrcSpan l _) a) f = realLocated (L l a) f
+
+-- | See 'located'
+realLocated ::
+  -- | Thing to enter
+  RealLocated a ->
+  -- | How to render inner value
+  (a -> R ()) ->
+  R ()
+realLocated (L l a) f = do
   spitPrecedingComments l
   withEnclosingSpan l $
-    switchLayout [RealSrcSpan l] (f a)
+    switchLayout [RealSrcSpan l Nothing] (f a)
   spitFollowingComments l
 
 -- | A version of 'located' with arguments flipped.
@@ -217,12 +230,29 @@
   txt "`"
 
 -- | Surround given entity by banana brackets (i.e., from arrow notation.)
-banana :: R () -> R ()
-banana = brackets_ True "(|" "|)" N
+banana :: BracketStyle -> R () -> R ()
+banana = brackets_ True "(|" "|)"
 
 -- | Surround given entity by curly braces @{@ and  @}@.
 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 ()
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
@@ -16,9 +16,9 @@
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe (listToMaybe)
 import qualified Data.Text as T
+import GHC.Types.SrcLoc
 import Ormolu.Parser.CommentStream
 import Ormolu.Printer.Internal
-import SrcLoc
 
 ----------------------------------------------------------------------------
 -- Top-level
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
@@ -22,6 +22,8 @@
     useRecordDot,
     inci,
     inciBy,
+    inciByFrac,
+    inciHalf,
     sitcc,
     Layout (..),
     enterLayout,
@@ -53,6 +55,9 @@
 
     -- * Annotations
     getAnns,
+
+    -- * Extensions
+    isExtensionEnabled,
   )
 where
 
@@ -66,13 +71,17 @@
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
 import Data.Text.Lazy.Builder
-import GHC
+import GHC.Data.EnumSet (EnumSet)
+import qualified GHC.Data.EnumSet as EnumSet
+import GHC.LanguageExtensions.Type
+import GHC.Parser.Annotation
+import GHC.Types.SrcLoc
+import GHC.Utils.Outputable (Outputable)
 import Ormolu.Config
 import Ormolu.Parser.Anns
 import Ormolu.Parser.CommentStream
 import Ormolu.Printer.SpanStream
 import Ormolu.Utils (showOutputable)
-import Outputable (Outputable)
 
 ----------------------------------------------------------------------------
 -- The 'R' monad
@@ -98,7 +107,9 @@
     rcCanUseBraces :: Bool,
     -- | Whether the source could have used the record dot preprocessor
     rcUseRecDot :: Bool,
-    rcPrinterOpts :: PrinterOptsTotal
+    rcPrinterOpts :: PrinterOptsTotal,
+    -- | Enabled extensions
+    rcExtensions :: EnumSet Extension
   }
 
 -- | State context of 'R'.
@@ -154,7 +165,7 @@
     OnNextLine
   deriving (Eq, Show)
 
--- | Run an 'R' monad.
+-- | Run 'R' monad.
 runR ::
   -- | Monad to run
   R () ->
@@ -167,9 +178,11 @@
   PrinterOptsTotal ->
   -- | Use Record Dot Syntax
   Bool ->
+  -- | Enabled extensions
+  EnumSet Extension ->
   -- | Resulting rendition
   Text
-runR (R m) sstream cstream anns printerOpts recDot =
+runR (R m) sstream cstream anns printerOpts recDot extensions =
   TL.toStrict . toLazyText . scBuilder $ execState (runReaderT m rc) sc
   where
     rc =
@@ -180,7 +193,8 @@
           rcAnns = anns,
           rcCanUseBraces = False,
           rcUseRecDot = recDot,
-          rcPrinterOpts = printerOpts
+          rcPrinterOpts = printerOpts,
+          rcExtensions = extensions
         }
     sc =
       SC
@@ -385,25 +399,35 @@
 useRecordDot :: R Bool
 useRecordDot = R (asks rcUseRecDot)
 
+-- | Like 'inci', but indents by exactly the given number of steps.
+inciBy :: Int -> R () -> R ()
+inciBy step (R m) = R (local modRC m)
+  where
+    modRC rc =
+      rc
+        { rcIndent = roundDownToNearest step (rcIndent rc) + step
+        }
+    roundDownToNearest r n = (n `div` r) * r
+
+-- | Like 'inci', but indents by the given fraction of a full step.
+inciByFrac :: Int -> R () -> R ()
+inciByFrac x m = do
+  indentStep <- R $ asks (runIdentity . poIndentation . rcPrinterOpts)
+  let step = indentStep `quot` x
+  inciBy step m
+
 -- | Increase indentation level by one indentation step for the inner
 -- computation. 'inci' should be used when a part of code must be more
 -- indented relative to the parts outside of 'inci' in order for the output
 -- to be valid Haskell. When layout is single-line there is no obvious
 -- effect, but with multi-line layout correct indentation levels matter.
 inci :: R () -> R ()
-inci = inciBy 1
+inci = inciByFrac 1
 
--- | Like 'inci', but indents by the given fraction of a full step.
-inciBy :: Int -> R () -> R ()
-inciBy x (R m) = do
-  step <- (`quot` x) <$> R (asks (runIdentity . poIndentation . rcPrinterOpts))
-  let modRC rc =
-        rc
-          { rcIndent = roundDownToNearest step (rcIndent rc) + step
-          }
-  R (local modRC m)
-  where
-    roundDownToNearest r n = (n `div` r) * r
+-- | In rare cases, we have to indent by a positive amount smaller
+-- than 'indentStep'.
+inciHalf :: R () -> R ()
+inciHalf = inciByFrac 2
 
 -- | Set indentation level for the inner computation equal to current
 -- column. This makes sure that the entire inner block is uniformly
@@ -595,3 +619,9 @@
 -- | Return 'True' if we can use braces in this context.
 canUseBraces :: R Bool
 canUseBraces = R (asks rcCanUseBraces)
+
+----------------------------------------------------------------------------
+-- Extensions
+
+isExtensionEnabled :: Extension -> R Bool
+isExtensionEnabled ext = R . asks $ EnumSet.member ext . rcExtensions
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
@@ -11,16 +11,22 @@
     p_qualName,
     p_infixDefHelper,
     p_hsDocString,
+    p_sourceText,
   )
 where
 
 import Control.Monad
-import Data.Functor
 import Data.List (intersperse, isPrefixOf)
 import qualified Data.Text as T
-import GHC hiding (GhcPs, IE)
-import Name (nameStableString)
-import OccName (OccName (..))
+import GHC.Hs.Doc
+import GHC.Hs.ImpExp
+import GHC.Parser.Annotation
+import GHC.Types.Basic
+import GHC.Types.Name (nameStableString)
+import GHC.Types.Name.Occurrence (OccName (..))
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
+import GHC.Unit.Module.Name
 import Ormolu.Config
 import Ormolu.Printer.Combinators
 import Ormolu.Utils
@@ -172,10 +178,10 @@
           Named name -> " $" <> T.pack name
         sequence_ $ intersperse (newline >> s) $ map txt' docLines
   single <-
-    getPrinterOpt poHaddockStyle <&> \case
-      HaddockSingleLine -> True
+    getPrinterOpt poHaddockStyle >>= \case
+      HaddockSingleLine -> pure True
       -- Use multiple single-line comments when the whole comment is indented
-      HaddockMultiLine -> maybe False ((> 1) . srcSpanStartCol) $ unSrcSpan l
+      HaddockMultiLine -> maybe False ((> 1) . srcSpanStartCol) <$> getSrcSpan l
   if single
     then do
       txt "--"
@@ -192,10 +198,16 @@
           txt "-}"
 
   when needsNewline newline
-  case l of
-    UnhelpfulSpan _ ->
+  getSrcSpan l >>= mapM_ (setSpanMark . HaddockSpan hstyle)
+  where
+    getSrcSpan = \case
       -- It's often the case that the comment itself doesn't have a span
       -- attached to it and instead its location can be obtained from
       -- nearest enclosing span.
-      getEnclosingSpan (const True) >>= mapM_ (setSpanMark . HaddockSpan hstyle)
-    RealSrcSpan spn -> setSpanMark (HaddockSpan hstyle spn)
+      UnhelpfulSpan _ -> getEnclosingSpan (const True)
+      RealSrcSpan spn _ -> pure $ Just spn
+
+p_sourceText :: SourceText -> R ()
+p_sourceText = \case
+  NoSourceText -> pure ()
+  SourceText s -> space >> txt (T.pack s)
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
@@ -14,8 +14,13 @@
 import Data.List (sort)
 import Data.List.NonEmpty (NonEmpty (..), (<|))
 import qualified Data.List.NonEmpty as NE
-import GHC hiding (InlinePragma)
-import OccName (occNameFS)
+import GHC.Hs.Binds
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Pat
+import GHC.Types.Name.Occurrence (occNameFS)
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Declaration.Annotation
@@ -107,7 +112,7 @@
   ValD NoExtField x -> p_valDecl x
   SigD NoExtField x -> p_sigDecl x
   InstD NoExtField x -> p_instDecl style x
-  DerivD NoExtField x -> p_derivDecl 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
@@ -122,7 +127,6 @@
       DocGroup n str -> p_hsDocString (Asterisk n) False (noLoc str)
   RoleAnnotD NoExtField x -> p_roleAnnot x
   KindSigD NoExtField s -> p_standaloneKindSig s
-  XHsDecl x -> noExtCon x
 
 p_tyClDecl :: FamilyStyle -> TyClDecl GhcPs -> R ()
 p_tyClDecl style = \case
@@ -132,7 +136,7 @@
     p_dataDecl
       Associated
       tcdLName
-      (tyVarsToTypes tcdTyVars)
+      (tyVarsToTyPats tcdTyVars)
       tcdFixity
       tcdDataDefn
   ClassDecl {..} ->
@@ -147,20 +151,13 @@
       tcdATs
       tcdATDefs
       tcdDocs
-  XTyClDecl x -> noExtCon x
 
 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
-  XInstDecl x -> noExtCon x
 
-p_derivDecl :: DerivDecl GhcPs -> R ()
-p_derivDecl = \case
-  d@DerivDecl {} -> p_standaloneDerivDecl d
-  XDerivDecl x -> noExtCon x
-
 -- | Determine if these declarations should be grouped together.
 groupedDecls ::
   LHsDecl GhcPs ->
@@ -188,6 +185,7 @@
     (KindSignature n, DataDeclaration n') -> n == n'
     (KindSignature n, ClassDeclaration n') -> n == n'
     (KindSignature n, FamilyDeclaration n') -> n == n'
+    (KindSignature n, TypeSynonym n') -> n == n'
     -- Special case for TH splices, we look at locations
     (Splice, Splice) -> not (separatedByBlank id l_x l_y)
     -- This looks only at Haddocks, normal comments are handled elsewhere
@@ -247,7 +245,8 @@
   DataDeclaration,
   ClassDeclaration,
   KindSignature,
-  FamilyDeclaration ::
+  FamilyDeclaration,
+  TypeSynonym ::
     RdrName -> HsDecl GhcPs
 pattern InlinePragma n <- SigD NoExtField (InlineSig NoExtField (L _ n) _)
 pattern SpecializePragma n <- SigD NoExtField (SpecSig NoExtField (L _ n) _ _)
@@ -256,9 +255,10 @@
 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 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) _ _ _)
 
 -- Declarations which can refer to multiple names
 
@@ -290,7 +290,7 @@
 defSigRdrNames _ = Nothing
 
 funRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
-funRdrNames (ValD NoExtField (FunBind NoExtField (L _ n) _ _ _)) = Just [n]
+funRdrNames (ValD NoExtField (FunBind NoExtField (L _ n) _ _)) = Just [n]
 funRdrNames (ValD NoExtField (PatBind NoExtField (L _ n) _ _)) = Just $ patBindNames n
 funRdrNames _ = Nothing
 
@@ -300,9 +300,7 @@
 
 warnSigRdrNames :: HsDecl GhcPs -> Maybe [RdrName]
 warnSigRdrNames (WarningD NoExtField (Warnings NoExtField _ ws)) = Just $
-  flip concatMap ws $ \case
-    L _ (Warning NoExtField ns _) -> map unLoc ns
-    L _ (XWarnDecl x) -> noExtCon x
+  flip concatMap ws $ \(L _ (Warning NoExtField ns _)) -> map unLoc ns
 warnSigRdrNames _ = Nothing
 
 patBindNames :: Pat GhcPs -> [RdrName]
@@ -321,7 +319,4 @@
 patBindNames (SigPat _ (L _ p) _) = patBindNames p
 patBindNames (NPat NoExtField _ _ _) = []
 patBindNames (NPlusKPat NoExtField (L _ n) _ _ _ _) = [n]
-patBindNames (ConPatIn _ d) = concatMap (patBindNames . unLoc) (hsConPatArgs d)
-patBindNames ConPatOut {} = notImplemented "ConPatOut" -- created by renamer
-patBindNames (CoPat NoExtField _ p _) = patBindNames p
-patBindNames (XPat x) = noExtCon x
+patBindNames (ConPat NoExtField _ d) = concatMap (patBindNames . unLoc) (hsConPatArgs d)
diff --git a/src/Ormolu/Printer/Meat/Declaration.hs-boot b/src/Ormolu/Printer/Meat/Declaration.hs-boot
--- a/src/Ormolu/Printer/Meat/Declaration.hs-boot
+++ b/src/Ormolu/Printer/Meat/Declaration.hs-boot
@@ -4,7 +4,8 @@
   )
 where
 
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 
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,18 +6,18 @@
   )
 where
 
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Declaration.Value
 
 p_annDecl :: AnnDecl GhcPs -> R ()
-p_annDecl = \case
-  HsAnnotation NoExtField _ annProv expr -> pragma "ANN" . inci $ do
+p_annDecl (HsAnnotation NoExtField _ annProv expr) =
+  pragma "ANN" . inci $ do
     p_annProv annProv
     breakpoint
     located expr p_hsExpr
-  XAnnDecl x -> noExtCon x
 
 p_annProv :: AnnProvenance (IdP GhcPs) -> R ()
 p_annProv = \case
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
@@ -8,12 +8,19 @@
   )
 where
 
-import Class
 import Control.Arrow
 import Control.Monad
 import Data.Foldable
-import Data.List (sortOn)
-import GHC
+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 GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Config
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
@@ -50,7 +57,7 @@
         )
           <$> catdefs
       allDecls =
-        snd <$> sortOn fst (sigs <> vals <> tyFams <> tyFamDefs <> docs)
+        snd <$> sortBy (leftmost_smallest `on` fst) (sigs <> vals <> tyFams <> tyFamDefs <> docs)
   txt "class"
   switchLayout combinedSpans $ do
     breakpoint
@@ -69,7 +76,6 @@
   unless (null allDecls) $ do
     breakpoint -- Ensure whitespace is added after where clause.
     inci (p_hsDeclsRespectGrouping Associated allDecls)
-p_classDecl _ _ (XLHsQTyVars c) _ _ _ _ _ _ _ = noExtCon c
 
 p_classContext :: LHsContext GhcPs -> R ()
 p_classContext ctx = unless (null (unLoc ctx)) $ do
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,3 +1,4 @@
+{-# LANGUAGE GADTs #-}
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
@@ -11,7 +12,14 @@
 import Control.Monad
 import Data.Maybe (isJust, maybeToList)
 import qualified Data.Text as Text
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Parser.Annotation
+import GHC.Types.Basic
+import GHC.Types.ForeignCall
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Config
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
@@ -24,7 +32,7 @@
   -- | Type constructor
   Located RdrName ->
   -- | Type patterns
-  [LHsType GhcPs] ->
+  HsTyPats GhcPs ->
   -- | Lexical fixity
   LexicalFixity ->
   -- | Data definition
@@ -37,27 +45,37 @@
   txt $ case style of
     Associated -> mempty
     Free -> " instance"
-  let constructorSpans = getLoc name : fmap getLoc tpats
-  switchLayout constructorSpans $ do
+  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
+      sigSpans = maybeToList . fmap getLoc $ dd_kindSig
+      declHeaderSpans = constructorSpans ++ sigSpans
+  switchLayout declHeaderSpans $ do
     breakpoint
-    inci $
-      p_infixDefHelper
-        (isInfix fixity)
-        True
-        (p_rdrName name)
-        (located' p_hsType <$> tpats)
-  case dd_kindSig of
-    Nothing -> return ()
-    Just k -> do
-      space
-      txt "::"
-      space
-      located k p_hsType
+    inci $ do
+      switchLayout constructorSpans $
+        p_infixDefHelper
+          (isInfix fixity)
+          True
+          (p_rdrName name)
+          (p_lhsTypeArg <$> tpats)
+      forM_ dd_kindSig $ \k -> do
+        space
+        txt "::"
+        breakpoint
+        inci $ 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
+        switchLayout declHeaderSpans $ do
           breakpoint
           txt "where"
         breakpoint
@@ -85,7 +103,6 @@
   unless (null $ unLoc dd_derivs) breakpoint
   inci . located dd_derivs $ \xs ->
     sep newline (located' p_hsDerivingClause) xs
-p_dataDecl _ _ _ _ (XHsDataDefn x) = noExtCon x
 
 p_conDecl ::
   Bool ->
@@ -97,7 +114,7 @@
     let conDeclSpn =
           fmap getLoc con_names
             <> [getLoc con_forall]
-            <> conTyVarsSpans con_qvars
+            <> fmap getLoc con_qvars
             <> maybeToList (fmap getLoc con_mb_cxt)
             <> conArgsSpans con_args
     switchLayout conDeclSpn $ do
@@ -116,25 +133,31 @@
                 then newline
                 else breakpoint
         interArgBreak
-        when (unLoc con_forall) $ do
-          p_forallBndrs ForallInvis p_hsTyVarBndr (hsq_explicit con_qvars)
-          interArgBreak
-        forM_ con_mb_cxt p_lhsContext
-        case con_args of
-          PrefixCon xs -> do
-            sep breakpoint (located' p_hsType) xs
-            unless (null xs) $ do
-              space
-              txt "->"
-              breakpoint
-          RecCon l -> do
-            located l p_conDeclFields
-            unless (null $ unLoc l) $ do
-              space
-              txt "->"
-              breakpoint
-          InfixCon _ _ -> notImplemented "InfixCon"
-        p_hsType (unLoc con_res_ty)
+        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 =
@@ -146,7 +169,7 @@
           getLoc con_name : conArgsSpans con_args
     switchLayout conDeclWithContextSpn $ do
       when (unLoc con_forall) $ do
-        p_forallBndrs ForallInvis p_hsTyVarBndr con_ex_tvs
+        p_forallBndrs ForAllInvis p_hsTyVarBndr con_ex_tvs
         breakpoint
         indent <- getPrinterOpt poIndentation
         vlayout (pure ()) . txt $ Text.replicate (indent - 2) " "
@@ -155,33 +178,27 @@
         PrefixCon xs -> do
           p_rdrName con_name
           unless (null xs) breakpoint
-          inci . sitcc $ sep breakpoint (sitcc . located' p_hsTypePostDoc) xs
+          inci . sitcc $ sep breakpoint (sitcc . located' p_hsTypePostDoc) (hsScaledThing <$> xs)
         RecCon l -> do
           p_rdrName con_name
           breakpoint
           inciIf (not singleConstRec) (located l p_conDeclFields)
-        InfixCon x y -> do
+        InfixCon (HsScaled _ x) (HsScaled _ y) -> do
           located x p_hsType
           breakpoint
           inci $ do
             p_rdrName con_name
             space
             located y p_hsType
-  XConDecl x -> noExtCon x
 
 conArgsSpans :: HsConDeclDetails GhcPs -> [SrcSpan]
 conArgsSpans = \case
   PrefixCon xs ->
-    getLoc <$> xs
+    getLoc . hsScaledThing <$> xs
   RecCon l ->
     [getLoc l]
   InfixCon x y ->
-    [getLoc x, getLoc y]
-
-conTyVarsSpans :: LHsQTyVars GhcPs -> [SrcSpan]
-conTyVarsSpans = \case
-  HsQTvs {..} -> getLoc <$> hsq_explicit
-  XLHsQTyVars x -> noExtCon x
+    getLoc . hsScaledThing <$> [x, y]
 
 p_lhsContext ::
   LHsContext GhcPs ->
@@ -198,7 +215,6 @@
 isGadt = \case
   ConDeclGADT {} -> True
   ConDeclH98 {} -> False
-  XConDecl {} -> False
 
 p_hsDerivingClause ::
   HsDerivingClause GhcPs ->
@@ -239,9 +255,6 @@
           txt "via"
           space
           located hsib_body p_hsType
-      ViaStrategy (XHsImplicitBndrs x) ->
-        noExtCon x
-p_hsDerivingClause (XHsDerivingClause x) = noExtCon x
 
 ----------------------------------------------------------------------------
 -- Helpers
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
@@ -6,15 +6,14 @@
   )
 where
 
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Type
 
 p_defaultDecl :: DefaultDecl GhcPs -> R ()
-p_defaultDecl = \case
-  DefaultDecl NoExtField ts -> do
-    txt "default"
-    breakpoint
-    inci . parens N $
-      sep commaDel (sitcc . located' p_hsType) ts
-  XDefaultDecl x -> noExtCon x
+p_defaultDecl (DefaultDecl NoExtField ts) = do
+  txt "default"
+  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
@@ -7,11 +7,12 @@
   )
 where
 
-import BasicTypes
 import Control.Monad
-import Data.Text
-import ForeignCall
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Types.ForeignCall
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Declaration.Signature
@@ -24,7 +25,6 @@
   fd@ForeignExport {fd_fe} -> do
     p_foreignExport fd_fe
     p_foreignTypeSig fd
-  XForeignDecl x -> noExtCon x
 
 -- | Printer for the last part of an import\/export, which is function name
 -- and type signature.
@@ -67,8 +67,3 @@
   space
   located (L loc cCallConv) atom
   located sourceText p_sourceText
-
-p_sourceText :: SourceText -> R ()
-p_sourceText = \case
-  NoSourceText -> pure ()
-  SourceText s -> space >> txt (pack s)
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
@@ -11,19 +11,22 @@
   )
 where
 
-import BasicTypes
 import Control.Arrow
 import Control.Monad
 import Data.Foldable
-import Data.List (sortOn)
-import GHC
+import Data.Function (on)
+import Data.List (sortBy)
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Types.Basic
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration
 import Ormolu.Printer.Meat.Declaration.Data
 import Ormolu.Printer.Meat.Declaration.TypeFamily
 import Ormolu.Printer.Meat.Type
-import Ormolu.Utils
 
 p_standaloneDerivDecl :: DerivDecl GhcPs -> R ()
 p_standaloneDerivDecl DerivDecl {..} = do
@@ -54,64 +57,51 @@
         inci (located hsib_body p_hsType)
         breakpoint
         instTypes True
-      ViaStrategy (XHsImplicitBndrs x) ->
-        noExtCon x
-p_standaloneDerivDecl (XDerivDecl _) = notImplemented "XDerivDecl"
 
 p_clsInstDecl :: ClsInstDecl GhcPs -> R ()
-p_clsInstDecl = \case
-  ClsInstDecl {..} -> do
-    txt "instance"
-    case cid_poly_ty of
-      HsIB {..} -> do
-        -- 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
-            tyFamInsts =
-              ( getLoc &&& fmap (InstD NoExtField . TyFamInstD NoExtField)
-              )
-                <$> cid_tyfam_insts
-            dataFamInsts =
-              ( getLoc &&& fmap (InstD NoExtField . DataFamInstD NoExtField)
-              )
-                <$> cid_datafam_insts
-            allDecls =
-              snd <$> sortOn fst (sigs <> vals <> tyFamInsts <> dataFamInsts)
-        located hsib_body $ \x -> do
-          breakpoint
-          inci $ do
-            match_overlap_mode cid_overlap_mode breakpoint
-            p_hsType x
-            unless (null allDecls) $ do
-              breakpoint
-              txt "where"
-        unless (null allDecls) . inci $ do
-          -- Ensure whitespace is added after where clause.
-          breakpoint
-          dontUseBraces $ p_hsDeclsRespectGrouping Associated allDecls
-      XHsImplicitBndrs x -> noExtCon x
-  XClsInstDecl x -> noExtCon x
+p_clsInstDecl ClsInstDecl {..} = 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
+      tyFamInsts =
+        ( getLoc &&& fmap (InstD NoExtField . TyFamInstD NoExtField)
+        )
+          <$> cid_tyfam_insts
+      dataFamInsts =
+        ( getLoc &&& fmap (InstD NoExtField . DataFamInstD NoExtField)
+        )
+          <$> cid_datafam_insts
+      allDecls =
+        snd <$> sortBy (leftmost_smallest `on` fst) (sigs <> vals <> tyFamInsts <> dataFamInsts)
+  located hsib_body $ \x -> do
+    breakpoint
+    inci $ do
+      match_overlap_mode cid_overlap_mode breakpoint
+      p_hsType x
+      unless (null allDecls) $ do
+        breakpoint
+        txt "where"
+  unless (null allDecls) . inci $ do
+    -- Ensure whitespace is added after where clause.
+    breakpoint
+    dontUseBraces $ p_hsDeclsRespectGrouping Associated allDecls
 
 p_tyFamInstDecl :: FamilyStyle -> TyFamInstDecl GhcPs -> R ()
-p_tyFamInstDecl style = \case
-  TyFamInstDecl {..} -> do
-    txt $ case style of
-      Associated -> "type"
-      Free -> "type instance"
-    breakpoint
-    inci (p_tyFamInstEqn tfid_eqn)
+p_tyFamInstDecl style TyFamInstDecl {..} = do
+  txt $ case style of
+    Associated -> "type"
+    Free -> "type instance"
+  breakpoint
+  inci (p_tyFamInstEqn tfid_eqn)
 
 p_dataFamInstDecl :: FamilyStyle -> DataFamInstDecl GhcPs -> R ()
-p_dataFamInstDecl style = \case
-  DataFamInstDecl {dfid_eqn = HsIB {hsib_body = FamEqn {..}}} ->
-    p_dataDecl style feqn_tycon (map typeArgToType feqn_pats) feqn_fixity feqn_rhs
-  DataFamInstDecl {dfid_eqn = HsIB {hsib_body = XFamEqn {}}} ->
-    notImplemented "XFamEqn"
-  DataFamInstDecl {dfid_eqn = XHsImplicitBndrs {}} ->
-    notImplemented "XHsImplicitBndrs"
+p_dataFamInstDecl style (DataFamInstDecl {dfid_eqn = HsIB {hsib_body = FamEqn {..}}}) =
+  p_dataDecl style feqn_tycon feqn_pats feqn_fixity feqn_rhs
 
 match_overlap_mode :: Maybe (Located OverlapMode) -> R () -> R ()
 match_overlap_mode overlap_mode layoutStrategy =
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
@@ -8,15 +8,16 @@
   )
 where
 
-import CoAxiom
-import GHC
+import GHC.Core.Coercion.Axiom
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+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 = \case
-  RoleAnnotDecl NoExtField l_name anns -> p_roleAnnot' l_name anns
-  XRoleAnnotDecl x -> noExtCon x
+p_roleAnnot (RoleAnnotDecl NoExtField l_name anns) = p_roleAnnot' l_name anns
 
 p_roleAnnot' :: Located RdrName -> [Located (Maybe Role)] -> R ()
 p_roleAnnot' l_name anns = do
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
@@ -1,14 +1,18 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
 
 module Ormolu.Printer.Meat.Declaration.Rule
   ( p_ruleDecls,
   )
 where
 
-import BasicTypes
 import Control.Monad (unless)
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Lit
+import GHC.Hs.Type
+import GHC.Types.Basic
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Declaration.Signature
@@ -16,38 +20,33 @@
 import Ormolu.Printer.Meat.Type
 
 p_ruleDecls :: RuleDecls GhcPs -> R ()
-p_ruleDecls = \case
-  HsRules NoExtField _ xs ->
-    pragma "RULES" $
-      sep breakpoint (sitcc . located' p_ruleDecl) xs
-  XRuleDecls x -> noExtCon x
+p_ruleDecls (HsRules NoExtField _ xs) =
+  pragma "RULES" $ sep breakpoint (sitcc . located' p_ruleDecl) xs
 
 p_ruleDecl :: RuleDecl GhcPs -> R ()
-p_ruleDecl = \case
-  HsRule NoExtField ruleName activation tyvars ruleBndrs lhs rhs -> do
-    located ruleName p_ruleName
-    space
-    p_activation activation
+p_ruleDecl (HsRule NoExtField ruleName activation tyvars 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
+  breakpoint
+  inci $ do
+    located lhs p_hsExpr
     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
-    breakpoint
+    equals
     inci $ do
-      located lhs p_hsExpr
-      space
-      equals
-      inci $ do
-        breakpoint
-        located rhs p_hsExpr
-  XRuleDecl x -> noExtCon x
+      breakpoint
+      located rhs p_hsExpr
 
 p_ruleName :: (SourceText, RuleName) -> R ()
 p_ruleName (_, name) = atom $ (HsString NoSourceText name :: HsLit GhcPs)
@@ -55,7 +54,6 @@
 p_ruleBndr :: RuleBndr GhcPs -> R ()
 p_ruleBndr = \case
   RuleBndr NoExtField x -> p_rdrName x
-  RuleBndrSig NoExtField x hswc -> parens N . sitcc $ do
+  RuleBndrSig NoExtField x HsPS {..} -> parens N . sitcc $ do
     p_rdrName x
-    p_typeAscription hswc
-  XRuleBndr x -> noExtCon x
+    p_typeAscription (HsWC NoExtField (HsIB NoExtField 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
@@ -11,10 +11,15 @@
   )
 where
 
-import BasicTypes
-import BooleanFormula
 import Control.Monad
-import GHC
+import GHC.Data.BooleanFormula
+import GHC.Hs.Binds
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Types.Basic
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Type
@@ -63,7 +68,6 @@
     then newline
     else breakpoint
   located t p_hsType
-p_typeAscription (XHsWildCardBndrs x) = noExtCon x
 
 p_patSynSig ::
   [Located RdrName] ->
@@ -105,7 +109,6 @@
     atom n
     space
     sitcc $ sep commaDel p_rdrName names
-  XFixitySig x -> noExtCon x
 
 p_inlineSig ::
   -- | Name
@@ -164,6 +167,7 @@
     txt "["
     atom n
     txt "]"
+  FinalActive -> notImplemented "FinalActive" -- NOTE(amesgen) is this unreachable or just not implemented?
 
 p_specInstSig :: LHsSigType GhcPs -> R ()
 p_specInstSig hsib =
@@ -222,7 +226,7 @@
     atom x
 
 p_standaloneKindSig :: StandaloneKindSig GhcPs -> R ()
-p_standaloneKindSig (StandaloneKindSig NoExtField name bndrs) = do
+p_standaloneKindSig (StandaloneKindSig NoExtField name (HsIB NoExtField sig)) = do
   txt "type"
   inci $ do
     space
@@ -230,7 +234,4 @@
     space
     txt "::"
     breakpoint
-    case bndrs of
-      HsIB NoExtField sig -> located sig p_hsType
-      XHsImplicitBndrs x -> noExtCon x
-p_standaloneKindSig (XStandaloneKindSig c) = noExtCon c
+    located sig p_hsType
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
@@ -5,11 +5,11 @@
   )
 where
 
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Declaration.Value (p_hsSplice)
 
 p_spliceDecl :: SpliceDecl GhcPs -> R ()
 p_spliceDecl = \case
   SpliceDecl NoExtField splice _explicit -> located splice p_hsSplice
-  XSpliceDecl x -> noExtCon x
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
@@ -7,7 +7,11 @@
   )
 where
 
-import GHC
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Types.Basic
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Type
@@ -37,4 +41,3 @@
     then newline
     else breakpoint
   inci (located t p_hsType)
-p_synDecl _ _ (XLHsQTyVars x) _ = noExtCon x
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,11 +11,14 @@
 
 import Control.Monad
 import Data.Maybe (isNothing)
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Types.Basic
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 import Ormolu.Printer.Meat.Type
-import Ormolu.Utils
 
 p_famDecl :: FamilyStyle -> FamilyDecl GhcPs -> R ()
 p_famDecl style FamilyDecl {fdTyVars = HsQTvs {..}, ..} = do
@@ -53,27 +56,20 @@
         Just eqs -> do
           newline
           sep newline (located' (inci . p_tyFamInstEqn)) eqs
-p_famDecl _ FamilyDecl {fdTyVars = XLHsQTyVars {}} =
-  notImplemented "XLHsQTyVars"
-p_famDecl _ (XFamilyDecl x) = noExtCon x
 
 p_familyResultSigL ::
   Located (FamilyResultSig GhcPs) ->
   Maybe (R ())
-p_familyResultSigL l =
-  case l of
-    L _ a -> case a of
-      NoSig NoExtField -> Nothing
-      KindSig NoExtField k -> Just $ do
-        txt "::"
-        breakpoint
-        located k p_hsType
-      TyVarSig NoExtField bndr -> Just $ do
-        equals
-        breakpoint
-        located bndr p_hsTyVarBndr
-      XFamilyResultSig x ->
-        noExtCon x
+p_familyResultSigL (L _ a) = case a of
+  NoSig NoExtField -> Nothing
+  KindSig NoExtField k -> Just $ do
+    txt "::"
+    breakpoint
+    located k p_hsType
+  TyVarSig NoExtField bndr -> Just $ do
+    equals
+    breakpoint
+    located bndr p_hsTyVarBndr
 
 p_injectivityAnn :: InjectivityAnn GhcPs -> R ()
 p_injectivityAnn (InjectivityAnn a bs) = do
@@ -90,22 +86,20 @@
   case feqn_bndrs of
     Nothing -> return ()
     Just bndrs -> do
-      p_forallBndrs ForallInvis p_hsTyVarBndr bndrs
+      p_forallBndrs ForAllInvis p_hsTyVarBndr bndrs
       breakpoint
   inciIf (not $ null feqn_bndrs) $ do
-    let famLhsSpn = getLoc feqn_tycon : fmap (getLoc . typeArgToType) feqn_pats
+    let famLhsSpn = getLoc feqn_tycon : fmap lhsTypeArgSrcSpan feqn_pats
     switchLayout famLhsSpn $
       p_infixDefHelper
         (isInfix feqn_fixity)
         True
         (p_rdrName feqn_tycon)
-        (located' p_hsType . typeArgToType <$> feqn_pats)
+        (p_lhsTypeArg <$> feqn_pats)
     space
     equals
     breakpoint
     inci (located feqn_rhs p_hsType)
-p_tyFamInstEqn HsIB {hsib_body = XFamEqn x} = noExtCon x
-p_tyFamInstEqn (XHsImplicitBndrs x) = noExtCon x
 
 ----------------------------------------------------------------------------
 -- 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,6 +1,7 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE ViewPatterns #-}
@@ -14,21 +15,33 @@
   )
 where
 
-import Bag (bagToList)
-import BasicTypes
 import Control.Monad
-import Ctype (is_space)
 import Data.Bool (bool)
 import Data.Char (isPunctuation, isSymbol)
 import Data.Data hiding (Infix, Prefix)
+import Data.Function (on)
 import Data.Functor ((<&>))
-import Data.List (intersperse, sortOn)
+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
-import OccName (occNameString)
+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.Config
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Internal
@@ -67,12 +80,11 @@
 
 p_valDecl :: HsBindLR GhcPs GhcPs -> R ()
 p_valDecl = \case
-  FunBind NoExtField funId funMatches _ _ -> p_funBind funId funMatches
+  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
-  XHsBindsLR x -> noExtCon x
 
 p_funBind ::
   Located RdrName ->
@@ -97,11 +109,13 @@
   -- | Match group
   MatchGroup GhcPs (Located body) ->
   R ()
-p_matchGroup' placer render style MG {..} = do
+p_matchGroup' placer render style mg@MG {..} = do
   let ob = case style of
-        Case -> id
-        LambdaCase -> id
+        Case -> bracesIfEmpty
+        LambdaCase -> bracesIfEmpty
         _ -> dontUseBraces
+        where
+          bracesIfEmpty = if isEmptyMatchGroup mg then useBraces else id
   -- Since we are forcing braces on 'sepSemi' based on 'ob', we have to
   -- restore the brace state inside the sepsemi.
   ub <- bool dontUseBraces useBraces <$> canUseBraces
@@ -116,8 +130,6 @@
         (matchStrictness m)
         m_pats
         m_grhss
-    p_Match (XMatch x) = noExtCon x
-p_matchGroup' _ _ _ (XMatchGroup x) = noExtCon x
 
 -- | Function id obtained through pattern matching on 'FunBind' should not
 -- be used to print the actual equations because the different ‘RdrNames’
@@ -249,11 +261,11 @@
                 else EqualSign
         sep newline (located' (p_grhs' placer render groupStyle)) grhssGRHSs
       p_where = do
-        let whereIsEmpty = GHC.isEmptyLocalBindsPR (unLoc grhssLocalBinds)
-        unless (GHC.eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do
+        let whereIsEmpty = eqEmptyLocalBinds (unLoc grhssLocalBinds)
+        unless (eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do
           breakpoint
           indentWhere <- getPrinterOpt poIndentWheres
-          bool (inciBy $ -2) id indentWhere $ txt "where"
+          bool (inciByFrac $ -2) id indentWhere $ txt "where"
           unless whereIsEmpty breakpoint
           inciIf indentWhere $ located grhssLocalBinds p_hsLocalBinds
   inciIf indentBody $ do
@@ -267,7 +279,6 @@
     switchLayout [patGrhssSpan] $
       placeHanging placement p_body
     inci p_where
-p_match' _ _ _ _ _ _ (XGRHSs x) = noExtCon x
 
 p_grhs :: GroupStyle -> GRHS GhcPs (LHsExpr GhcPs) -> R ()
 p_grhs = p_grhs' exprPlacement p_hsExpr
@@ -306,55 +317,53 @@
         Nothing -> Nothing
         Just gs -> (Just . getLoc . NE.last) gs
     p_body = located body render
-p_grhs' _ _ _ (XGRHS x) = noExtCon x
 
 p_hsCmd :: HsCmd GhcPs -> R ()
-p_hsCmd = \case
-  HsCmdArrApp NoExtField body input arrType _ -> do
-    located body p_hsExpr
+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 of
-        HsFirstOrderApp -> txt "-<"
-        HsHigherOrderApp -> txt "-<<"
+      case (arrType, rightToLeft) of
+        (HsFirstOrderApp, True) -> txt "-<"
+        (HsHigherOrderApp, True) -> txt "-<<"
+        (HsFirstOrderApp, False) -> txt ">-"
+        (HsHigherOrderApp, False) -> txt ">>-"
       placeHanging (exprPlacement (unLoc input)) $
-        located input p_hsExpr
-  HsCmdArrForm NoExtField form Prefix _ cmds -> banana $ do
+        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
-    space
-    located form p_hsExpr
-    placeHanging (cmdTopPlacement (unLoc right)) $
-      located right p_hsCmdTop
+  HsCmdArrForm NoExtField form Infix _ [left, right] ->
+    let opTree = OpBranch (cmdOpTree left) form (cmdOpTree right)
+     in p_cmdOpTree (reassociateOpTree getOpName opTree)
   HsCmdArrForm NoExtField _ Infix _ _ -> notImplemented "HsCmdArrForm"
-  HsCmdApp {} ->
-    -- XXX Does this ever occur in the syntax tree? It does not seem like it
-    -- does. Open an issue and ping @yumiova if this ever occurs in output.
-    notImplemented "HsCmdApp"
+  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 $ sitcc $ 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))
-  HsCmdWrap {} -> notImplemented "HsCmdWrap"
-  XCmd x -> noExtCon x
+    p_stmts cmdPlacement (p_hsCmd' S) es
 
 p_hsCmdTop :: HsCmdTop GhcPs -> R ()
-p_hsCmdTop = \case
-  HsCmdTop NoExtField cmd -> located cmd p_hsCmd
-  XCmdTop x -> noExtCon x
+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.
@@ -367,7 +376,7 @@
 withSpacing f l = located l $ \x -> do
   case getLoc l of
     UnhelpfulSpan _ -> f x
-    RealSrcSpan currentSpn -> do
+    RealSrcSpan currentSpn _ -> do
       getSpanMark >>= \case
         -- Spacing before comments will be handled by the code
         -- that prints comments, so we just have to deal with
@@ -399,19 +408,15 @@
   R ()
 p_stmt' placer render = \case
   LastStmt NoExtField body _ _ -> located body render
-  BindStmt NoExtField p f _ _ -> do
+  BindStmt NoExtField p f@(L l x) -> do
     located p p_pat
     space
     txt "<-"
     let loc = getLoc p
-        placement =
-          case f of
-            L l' x ->
-              if isOneLineSpan
-                (mkSrcSpan (srcSpanEnd loc) (srcSpanStart l'))
-                then placer x
-                else Normal
-    switchLayout [loc, getLoc f] $
+        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
@@ -457,8 +462,23 @@
     txt "rec"
     space
     sitcc $ sepSemi (withSpacing (p_stmt' placer render)) recS_stmts
-  XStmtLR c -> noExtCon c
 
+p_stmts ::
+  Data body =>
+  -- | Placer
+  (body -> Placement) ->
+  -- | Render
+  (body -> R ()) ->
+  -- | Statements to render
+  Located [Located (Stmt GhcPs (Located body))] ->
+  R ()
+p_stmts placer render es = do
+  breakpoint
+  ub <- layoutToBraces <$> getLayout
+  inci . located es $
+    sepSemi
+      (ub . withSpacing (p_stmt' placer render))
+
 gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]]
 gatherStmt (L _ (ParStmt NoExtField block _ _)) =
   foldr ((<>) . gatherStmtBlock) [] block
@@ -469,7 +489,6 @@
 gatherStmtBlock :: ParStmtBlock GhcPs GhcPs -> [[ExprLStmt GhcPs]]
 gatherStmtBlock (ParStmtBlock _ stmts _ _) =
   foldr (liftAppend . gatherStmt) [] stmts
-gatherStmtBlock (XParStmtBlock x) = noExtCon x
 
 p_hsLocalBinds :: HsLocalBindsLR GhcPs GhcPs -> R ()
 p_hsLocalBinds = \case
@@ -490,7 +509,7 @@
         p_item' (p, item) =
           positionToBracing p $
             withSpacing (either p_valDecl p_sigDecl) item
-        binds = sortOn (srcSpanStart . getLoc) items
+        binds = sortBy (leftmost_smallest `on` getLoc) items
     sitcc $ sepSemi p_item' (attachRelativePos binds)
   HsValBinds NoExtField _ -> notImplemented "HsValBinds"
   HsIPBinds NoExtField (IPBinds NoExtField xs) ->
@@ -504,11 +523,8 @@
         p_ipBind (IPBind NoExtField (Right _) _) =
           -- Should only occur after the type checker
           notImplemented "IPBind _ (Right _) _"
-        p_ipBind (XIPBind x) = noExtCon x
      in sepSemi (located' p_ipBind) xs
-  HsIPBinds NoExtField _ -> notImplemented "HsIpBinds"
   EmptyLocalBinds NoExtField -> return ()
-  XHsLocalBindsLR x -> noExtCon x
 
 p_hsRecField ::
   HsRecField' RdrName (LHsExpr GhcPs) ->
@@ -530,13 +546,12 @@
 p_hsExpr' :: BracketStyle -> HsExpr GhcPs -> R ()
 p_hsExpr' s = \case
   HsVar NoExtField name -> p_rdrName name
-  HsUnboundVar NoExtField v -> atom (unboundVarOcc v)
+  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
-      XAmbiguousFieldOcc xx -> noExtCon xx
   HsOverLabel NoExtField _ v -> do
     txt "#"
     atom v
@@ -551,10 +566,8 @@
       r -> atom r
   HsLam NoExtField mgroup ->
     p_matchGroup Lambda mgroup
-  HsLamCase NoExtField mgroup -> do
-    txt "\\case"
-    breakpoint
-    inci (p_matchGroup LambdaCase 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
@@ -583,16 +596,22 @@
     case placement of
       Normal -> do
         let -- Usually we want to bump indentation for arguments for the
-            -- sake of readability. However, when the function itself is a
-            -- do-block or case expression it is not a good idea. It seems
-            -- to be safe to always bump indentation when the function
-            -- expression is parenthesised.
-            doIndent =
-              case func of
-                L _ (HsPar NoExtField _) -> True
-                L _ (HsAppType NoExtField _ _) -> True
-                L _ (HsMultiIf NoExtField _) -> True
-                L spn _ -> isOneLineSpan spn
+            -- 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) = case unLoc func of
+                HsDo {} -> inciBy 2
+                _ -> inci
+              | otherwise = case unLoc func of
+                HsDo {} -> inciHalf
+                HsCase {} -> inciHalf
+                HsLamCase {} -> inciHalf
+                _ -> inci
         ub <-
           getLayout <&> \case
             SingleLine -> useBraces
@@ -600,8 +619,8 @@
         ub $ do
           located func (p_hsExpr' s)
           breakpoint
-          inciIf doIndent $ sep breakpoint (located' p_hsExpr) initp
-        inciIf doIndent $ do
+          indentArg $ sep breakpoint (located' p_hsExpr) initp
+        indentArg $ do
           unless (null initp) breakpoint
           located lastp p_hsExpr
       Hanging -> do
@@ -609,7 +628,7 @@
           located func (p_hsExpr' s)
           breakpoint
           sep breakpoint (located' p_hsExpr) initp
-        placeHanging placement $
+        placeHanging placement . dontUseBraces $
           located lastp p_hsExpr
   HsAppType NoExtField e a -> do
     located e p_hsExpr
@@ -625,9 +644,16 @@
   OpApp NoExtField x op y -> do
     let opTree = OpBranch (exprOpTree x) op (exprOpTree y)
     p_exprOpTree s (reassociateOpTree getOpName opTree)
-  NegApp NoExtField e _ -> do
+  NegApp NoExtField e NoExtField -> do
+    negativeLiterals <- isExtensionEnabled NegativeLiterals
+    let isLiteral = case unLoc e of
+          HsLit {} -> True
+          HsOverLit {} -> True
+          _ -> False
     txt "-"
-    space
+    -- 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 -> do
     parens s $ sitcc $ located e $ dontUseBraces . p_hsExpr
@@ -649,7 +675,6 @@
         p_arg = \case
           Present NoExtField x -> located x p_hsExprListItem
           Missing NoExtField -> pure ()
-          XTupArg x -> noExtCon x
         p_larg = sitcc . located' p_arg
         parens' =
           case boxity of
@@ -666,7 +691,7 @@
     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' ->
+  HsIf NoExtField if' then' else' ->
     p_if exprPlacement p_hsExpr if' then' else'
   HsMultiIf NoExtField guards -> do
     txt "if"
@@ -675,14 +700,10 @@
   HsLet NoExtField localBinds e ->
     p_let p_hsExpr localBinds e
   HsDo NoExtField ctx es -> do
-    let doBody header = 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)
+          p_stmts exprPlacement (p_hsExpr' S) es
         compBody = brackets N . located es $ \xs -> do
           let p_parBody =
                 sep
@@ -706,10 +727,10 @@
           space
           p_parBody lists
     case ctx of
-      DoExpr -> doBody "do"
-      MDoExpr -> doBody "mdo"
+      DoExpr moduleName -> doBody moduleName "do"
+      MDoExpr moduleName -> doBody moduleName "mdo"
       ListComp -> compBody
-      MonadComp -> notImplemented "MonadComp"
+      MonadComp -> compBody
       ArrowExpr -> notImplemented "ArrowExpr"
       GhciStmtCtxt -> notImplemented "GhciStmtCtxt"
       PatGuard _ -> notImplemented "PatGuard"
@@ -726,7 +747,6 @@
           (f :: HsRecField GhcPs (LHsExpr GhcPs))
             { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of
                 FieldOcc _ n -> n
-                XFieldOcc x -> noExtCon x
             }
         fields = located' (p_hsRecField . updName) <$> rec_flds
         dotdot =
@@ -739,20 +759,24 @@
     located rupd_expr p_hsExpr
     useRecordDot' <- useRecordDot
     let mrs sp = case getLoc sp of
-          RealSrcSpan r -> Just r
+          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) breakpointPreRecordBrace
     let updName f =
           (f :: HsRecUpdField GhcPs)
             { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of
                 Ambiguous _ n -> n
                 Unambiguous _ n -> n
-                XAmbiguousFieldOcc x -> noExtCon x
             }
-    inci . braces N $
+        updBraces =
+          if useRecordDot' && isPluginForm
+            then recordDotBraces
+            else inci . braces N
+    updBraces $
       sep
         commaDel
         (sitcc . located' (p_hsRecField . updName))
@@ -763,8 +787,6 @@
     txt "::"
     breakpoint
     inci $ located hsib_body p_hsType
-  ExprWithTySig NoExtField _ HsWC {hswc_body = XHsImplicitBndrs x} -> noExtCon x
-  ExprWithTySig NoExtField _ (XHsWildCardBndrs x) -> noExtCon x
   ArithSeq NoExtField _ x ->
     case x of
       From from -> brackets s $ do
@@ -787,18 +809,6 @@
         txt ".."
         space
         located to p_hsExpr
-  HsSCC NoExtField _ name x -> do
-    txt "{-# SCC "
-    atom name
-    txt " #-}"
-    breakpoint
-    located x p_hsExpr
-  HsCoreAnn NoExtField _ value x -> do
-    txt "{-# CORE "
-    atom value
-    txt " #-}"
-    breakpoint
-    located x p_hsExpr
   HsBracket NoExtField x -> p_hsBracket x
   HsRnBracketOut {} -> notImplemented "HsRnBracketOut"
   HsTcBracketOut {} -> notImplemented "HsTcBracketOut"
@@ -818,9 +828,14 @@
     inci (located e p_hsExpr)
   HsTick {} -> notImplemented "HsTick"
   HsBinTick {} -> notImplemented "HsBinTick"
-  HsTickPragma {} -> notImplemented "HsTickPragma"
-  HsWrap {} -> notImplemented "HsWrap"
-  XExpr x -> noExtCon x
+  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
@@ -872,7 +887,6 @@
           space
           p_rdrName r
       inci rhs
-p_patSynBind (XPatSynBind x) = noExtCon x
 
 p_case ::
   Data body =>
@@ -894,6 +908,20 @@
   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
@@ -965,7 +993,7 @@
     parens' $ sep commaDel (sitcc . located' p_pat) pats
   SumPat NoExtField pat tag arity ->
     p_unboxedSum S tag arity (located pat p_pat)
-  ConPatIn pat details ->
+  ConPat NoExtField pat details ->
     case details of
       PrefixCon xs -> sitcc $ do
         p_rdrName pat
@@ -990,7 +1018,6 @@
             p_rdrName pat
             space
             located r p_pat
-  ConPatOut {} -> notImplemented "ConPatOut" -- presumably created by renamer?
   ViewPat NoExtField expr pat -> sitcc $ do
     located expr p_hsExpr
     space
@@ -999,7 +1026,12 @@
     inci (located pat p_pat)
   SplicePat NoExtField splice -> p_hsSplice splice
   LitPat NoExtField p -> atom p
-  NPat NoExtField v _ _ -> located v (atom . ol_val)
+  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
@@ -1007,11 +1039,9 @@
       txt "+"
       space
       located k (atom . ol_val)
-  SigPat NoExtField pat hswc -> do
+  SigPat NoExtField pat HsPS {..} -> do
     located pat p_pat
-    p_typeAscription hswc
-  CoPat {} -> notImplemented "CoPat" -- apparently created at some later stage
-  XPat x -> noExtCon x
+    p_typeAscription (HsWC NoExtField (HsIB NoExtField hsps_body))
 
 p_pat_hsRecField :: HsRecField' (FieldOcc GhcPs) (LPat GhcPs) -> R ()
 p_pat_hsRecField HsRecField {..} = do
@@ -1051,8 +1081,6 @@
     atom str
     txt "|]"
   HsSpliced {} -> notImplemented "HsSpliced"
-  HsSplicedT {} -> notImplemented "HsSplicedT"
-  XSplice x -> noExtCon x
 
 p_hsSpliceTH ::
   -- | Typed splice?
@@ -1063,13 +1091,10 @@
   SpliceDecoration ->
   R ()
 p_hsSpliceTH isTyped expr = \case
-  HasParens -> do
-    txt decoSymbol
-    parens N (located expr (sitcc . p_hsExpr))
-  HasDollar -> do
+  DollarSplice -> do
     txt decoSymbol
     located expr (sitcc . p_hsExpr)
-  NoParens ->
+  BareSplice ->
     located expr (sitcc . p_hsExpr)
   where
     decoSymbol = if isTyped then "$$" else "$"
@@ -1083,9 +1108,9 @@
           _ -> "e"
     quote name (located expr p_hsExpr)
   PatBr NoExtField pat -> located pat (quote "p" . p_pat)
-  DecBrL NoExtField decls -> quote "d" (p_hsDecls Free decls)
+  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 p_hsType)
+  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
@@ -1107,7 +1132,6 @@
     located expr p_hsExpr
     breakpoint'
     txt "||]"
-  XBracket x -> noExtCon x
   where
     quote :: Text -> R () -> R ()
     quote name body = do
@@ -1119,6 +1143,19 @@
         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.
@@ -1188,7 +1225,6 @@
 getGRHSSpan :: GRHS GhcPs (Located body) -> SrcSpan
 getGRHSSpan (GRHS NoExtField guards body) =
   combineSrcSpans' $ getLoc body :| map getLoc guards
-getGRHSSpan (XGRHS x) = noExtCon x
 
 -- | Place a thing that may have a hanging form. This function handles how
 -- to separate it from preceding expressions and whether to bump indentation
@@ -1217,13 +1253,12 @@
 cmdPlacement = \case
   HsCmdLam NoExtField _ -> Hanging
   HsCmdCase NoExtField _ _ -> Hanging
+  HsCmdLamCase NoExtField _ -> Hanging
   HsCmdDo NoExtField _ -> Hanging
   _ -> Normal
 
 cmdTopPlacement :: HsCmdTop GhcPs -> Placement
-cmdTopPlacement = \case
-  HsCmdTop NoExtField (L _ x) -> cmdPlacement x
-  XCmdTop x -> noExtCon x
+cmdTopPlacement (HsCmdTop NoExtField (L _ x)) = cmdPlacement x
 
 -- | Check if given expression has a hanging form.
 exprPlacement :: HsExpr GhcPs -> Placement
@@ -1236,8 +1271,8 @@
     _ -> Normal
   HsLamCase NoExtField _ -> Hanging
   HsCase NoExtField _ _ -> Hanging
-  HsDo NoExtField DoExpr _ -> Hanging
-  HsDo NoExtField MDoExpr _ -> Hanging
+  HsDo NoExtField (DoExpr _) _ -> Hanging
+  HsDo NoExtField (MDoExpr _) _ -> Hanging
   OpApp NoExtField _ op y ->
     case (fmap getOpNameStr . getOpName . unLoc) op of
       Just "$" -> exprPlacement (unLoc y)
@@ -1277,28 +1312,14 @@
   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
+  let placement = opBranchPlacement exprPlacement x y
       -- 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
+  ub <- opBranchBraceStyle placement
+  let opNameStr = (fmap getOpNameStr . getOpName . unLoc) op
       gotDollar = opNameStr == Just "$"
       gotColon = opNameStr == Just ":"
       gotRecordDot = isRecordDot (unLoc op) (opTreeLoc y)
@@ -1308,7 +1329,7 @@
       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) ->
+        (RealSrcSpan treeSpan _, RealSrcSpan opSpan _) ->
           srcSpanEndCol treeSpan /= srcSpanStartCol opSpan
         _ -> False
       isDoBlock = \case
@@ -1317,7 +1338,7 @@
   useRecordDot' <- useRecordDot
   if
       | gotColon -> do
-        lhs
+        ub lhs
         space
         p_op
         case placement of
@@ -1354,6 +1375,52 @@
           OpNode (unLoc -> HsDo _ _ _) | isOneLineSpan (opTreeLoc x) -> breakpoint >> opAndRhs
           _ -> placeHanging placement opAndRhs
 
+pattern CmdTopCmd :: HsCmd GhcPs -> LHsCmdTop GhcPs
+pattern CmdTopCmd cmd <- (L _ (HsCmdTop NoExtField (L _ cmd)))
+
+cmdOpTree :: LHsCmdTop GhcPs -> OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs)
+cmdOpTree = \case
+  CmdTopCmd (HsCmdArrForm NoExtField op Infix _ [x, y]) ->
+    OpBranch (cmdOpTree x) op (cmdOpTree y)
+  n -> OpNode n
+
+p_cmdOpTree :: OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs) -> R ()
+p_cmdOpTree = \case
+  OpNode n -> located n p_hsCmdTop
+  OpBranch x op y -> do
+    let placement = opBranchPlacement cmdTopPlacement x y
+    ub <- opBranchBraceStyle placement
+    ub $ p_cmdOpTree x
+    placeHanging placement $ do
+      located op p_hsExpr
+      space
+      p_cmdOpTree y
+
+opBranchPlacement ::
+  -- | Placement of nodes
+  (ty -> Placement) ->
+  -- | Left branch
+  OpTree (Located ty) op ->
+  -- | Right branch
+  OpTree (Located ty) op ->
+  Placement
+opBranchPlacement f x y
+  -- 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.
+  | isOneLineSpan (mkSrcSpan (srcSpanStart (opTreeLoc x)) (srcSpanStart (opTreeLoc y))),
+    OpNode (L _ n) <- y =
+    f n
+  | otherwise = Normal
+
+opBranchBraceStyle :: Placement -> R (R () -> R ())
+opBranchBraceStyle placement =
+  getLayout <&> \case
+    SingleLine -> useBraces
+    MultiLine -> case placement of
+      Hanging -> useBraces
+      Normal -> dontUseBraces
+
 -- | Return 'True' if given expression is a record-dot operator expression.
 isRecordDot ::
   -- | Operator expression
@@ -1361,8 +1428,8 @@
   -- | 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) ->
+isRecordDot op (RealSrcSpan ySpan _) = case op of
+  HsVar NoExtField (L (RealSrcSpan opSpan _) opName) ->
     (getOpNameStr opName == ".") && (srcSpanEndCol opSpan == srcSpanStartCol ySpan)
   _ -> False
 isRecordDot _ _ = False
@@ -1373,7 +1440,7 @@
   e <- getEnclosingSpan (const True)
   case e of
     Nothing -> return []
-    Just e' -> getAnns (RealSrcSpan e')
+    Just e' -> getAnns (RealSrcSpan e' Nothing)
 
 -- | For use before record braces. Collapse to empty if not 'poRecordBraceSpace'.
 breakpointPreRecordBrace :: R ()
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
@@ -7,7 +7,10 @@
   )
 where
 
-import GHC
+import GHC.Hs.Binds
+import GHC.Hs.Expr
+import GHC.Hs.Extension
+import GHC.Hs.Pat
 import Ormolu.Printer.Combinators
 
 p_valDecl :: HsBindLR GhcPs GhcPs -> R ()
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
@@ -7,22 +7,23 @@
   )
 where
 
-import BasicTypes
 import Data.Foldable
 import Data.Text (Text)
-import GHC
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Types.Basic
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
 
 p_warnDecls :: WarnDecls GhcPs -> R ()
 p_warnDecls (Warnings NoExtField _ warnings) =
   traverse_ (located' p_warnDecl) warnings
-p_warnDecls (XWarnDecls x) = noExtCon x
 
 p_warnDecl :: WarnDecl GhcPs -> R ()
 p_warnDecl (Warning NoExtField functions warningTxt) =
   p_topLevelWarning functions warningTxt
-p_warnDecl (XWarnDecl x) = noExtCon x
 
 p_moduleWarning :: WarningTxt -> R ()
 p_moduleWarning wtxt = do
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
@@ -12,7 +12,11 @@
 
 import Control.Monad
 import qualified Data.Text as T
-import GHC
+import GHC.Hs.Extension
+import GHC.Hs.ImpExp
+import GHC.LanguageExtensions.Type
+import GHC.Types.SrcLoc
+import GHC.Unit.Types
 import Ormolu.Config (poDiffFriendlyImportExport)
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Meat.Common
@@ -31,11 +35,12 @@
       (\(p, l) -> sitcc (located l (p_lie layout p)))
       (attachRelativePos xs)
 
-p_hsmodImport :: Bool -> ImportDecl GhcPs -> R ()
-p_hsmodImport useQualifiedPost ImportDecl {..} = do
+p_hsmodImport :: ImportDecl GhcPs -> R ()
+p_hsmodImport ImportDecl {..} = do
+  useQualifiedPost <- isExtensionEnabled ImportQualifiedPost
   txt "import"
   space
-  when ideclSource (txt "{-# SOURCE #-}")
+  when (ideclSource == IsBoot) (txt "{-# SOURCE #-}")
   space
   when ideclSafe (txt "safe")
   space
@@ -75,7 +80,6 @@
             (\(p, l) -> sitcc (located l (p_lie layout p)))
             (attachRelativePos xs)
     newline
-p_hsmodImport _ (XImportDecl x) = noExtCon x
 
 p_lie :: Layout -> RelativePos -> IE GhcPs -> R ()
 p_lie encLayout relativePos = \case
@@ -116,7 +120,6 @@
   IEDoc NoExtField str ->
     p_hsDocString Pipe False (noLoc str)
   IEDocNamed NoExtField str -> txt $ "-- $" <> T.pack str
-  XIE x -> noExtCon x
   where
     p_comma =
       case encLayout of
@@ -145,7 +148,7 @@
       txt "("
       breakpoint'
       sitcc body
-      vlayout (txt ")") (inciBy (-1) trailingParen)
+      vlayout (txt ")") (inciByFrac (-1) trailingParen)
     False -> sitcc $ do
       txt "("
       body
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
@@ -9,13 +9,12 @@
 where
 
 import Control.Monad
-import qualified Data.Text as T
-import GHC
+import GHC.Hs
+import GHC.Types.SrcLoc
 import Ormolu.Config
 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
@@ -28,23 +27,15 @@
 p_hsModule ::
   -- | Stack header
   Maybe (RealLocated Comment) ->
-  -- | Shebangs
-  [Shebang] ->
   -- | Pragmas and the associated comments
   [([RealLocated Comment], Pragma)] ->
-  -- | Whether to use postfix qualified imports
-  Bool ->
   -- | AST to print
-  HsModule GhcPs ->
+  HsModule ->
   R ()
-p_hsModule mstackHeader shebangs pragmas qualifiedPost HsModule {..} = do
+p_hsModule mstackHeader pragmas HsModule {..} = do
   let deprecSpan = maybe [] (\(L s _) -> [s]) hsmodDeprecMessage
       exportSpans = maybe [] (\(L s _) -> [s]) hsmodExports
   switchLayout (deprecSpan <> exportSpans) $ do
-    forM_ shebangs $ \(Shebang x) ->
-      located x $ \shebang -> do
-        txt (T.pack shebang)
-        newline
     forM_ mstackHeader $ \(L spn comment) -> do
       spitCommentNow spn comment
       newline
@@ -77,7 +68,7 @@
     newline
     preserveGroups <- getPrinterOpt poRespectful
     forM_ (normalizeImports preserveGroups hsmodImports) $ \importGroup -> do
-      forM_ importGroup (located' (p_hsmodImport qualifiedPost))
+      forM_ importGroup (located' p_hsmodImport)
       newline
     declNewline
     switchLayout (getLoc <$> hsmodDecls) $ do
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
@@ -13,11 +13,11 @@
 import qualified Data.List as L
 import Data.Maybe (listToMaybe)
 import qualified Data.Text as T
+import GHC.Types.SrcLoc
 import Ormolu.Parser.CommentStream
 import Ormolu.Parser.Pragma (Pragma (..))
 import Ormolu.Printer.Combinators
 import Ormolu.Printer.Comments
-import SrcLoc
 
 -- | Pragma classification.
 data PragmaTy
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,5 +1,6 @@
 {-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Rendering of types.
@@ -9,14 +10,23 @@
     hasDocStrings,
     p_hsContext,
     p_hsTyVarBndr,
+    ForAllVisibility (..),
     p_forallBndrs,
     p_conDeclFields,
+    p_lhsTypeArg,
     tyVarsToTypes,
+    tyVarsToTyPats,
   )
 where
 
 import Data.Data (Data)
-import GHC hiding (isPromoted)
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs.Type
+import GHC.Types.Basic hiding (isPromoted)
+import GHC.Types.Name.Reader
+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)
@@ -36,8 +46,10 @@
 
 p_hsType' :: Bool -> TypeDocStyle -> HsType GhcPs -> R ()
 p_hsType' multilineArgs docStyle = \case
-  HsForAllTy NoExtField visibility bndrs t -> do
-    p_forallBndrs visibility p_hsTyVarBndr bndrs
+  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
     interArgBreak
     p_hsTypeR (unLoc t)
   HsQualTy NoExtField qs t -> do
@@ -82,10 +94,17 @@
     inci $ do
       txt "@"
       located kd p_hsType
-  HsFunTy NoExtField x y@(L _ y') -> do
+  HsFunTy NoExtField arrow x y@(L _ y') -> do
     located x p_hsType
     space
-    txt "->"
+    case arrow of
+      HsUnrestrictedArrow _ -> txt "->"
+      HsLinearArrow _ -> txt "%1 ->"
+      HsExplicitMult _ mult -> do
+        txt "%"
+        p_hsTypeR (unLoc mult)
+        space
+        txt "->"
     interArgBreak
     case y' of
       HsFunTy {} -> p_hsTypeR y'
@@ -170,6 +189,7 @@
   XHsType (NHsCoreTy t) -> atom t
   where
     isPromoted = \case
+      HsAppTy _ (L _ f) _ -> isPromoted f
       HsTyVar _ IsPromoted _ -> True
       HsExplicitTupleTy {} -> True
       HsExplicitListTy {} -> True
@@ -185,7 +205,9 @@
 hasDocStrings :: HsType GhcPs -> Bool
 hasDocStrings = \case
   HsDocTy {} -> True
-  HsFunTy _ (L _ x) (L _ y) -> hasDocStrings x || hasDocStrings y
+  HsFunTy _ _ (L _ x) (L _ y) -> hasDocStrings x || hasDocStrings y
+  HsForAllTy _ _ (L _ x) -> hasDocStrings x
+  HsQualTy _ _ (L _ x) -> hasDocStrings x
   _ -> False
 
 p_hsContext :: HsContext GhcPs -> R ()
@@ -194,22 +216,34 @@
   [x] -> located x p_hsType
   xs -> parens N $ sep commaDel (sitcc . located' p_hsType) xs
 
-p_hsTyVarBndr :: HsTyVarBndr GhcPs -> R ()
+class IsInferredTyVarBndr flag where
+  isInferred :: flag -> Bool
+
+instance IsInferredTyVarBndr () where
+  isInferred () = False
+
+instance IsInferredTyVarBndr Specificity where
+  isInferred = \case
+    InferredSpec -> True
+    SpecifiedSpec -> False
+
+p_hsTyVarBndr :: IsInferredTyVarBndr flag => HsTyVarBndr flag GhcPs -> R ()
 p_hsTyVarBndr = \case
-  UserTyVar NoExtField x ->
-    p_rdrName x
-  KindedTyVar NoExtField l k -> parens N . sitcc $ do
+  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 . sitcc $ do
     located l atom
     space
     txt "::"
     breakpoint
     inci (located k p_hsType)
-  XTyVarBndr x -> noExtCon x
 
+data ForAllVisibility = ForAllInvis | ForAllVis
+
 -- | Render several @forall@-ed variables.
-p_forallBndrs :: Data a => ForallVisFlag -> (a -> R ()) -> [Located a] -> R ()
-p_forallBndrs ForallInvis _ [] = txt "forall."
-p_forallBndrs ForallVis _ [] = txt "forall ->"
+p_forallBndrs :: Data a => ForAllVisibility -> (a -> R ()) -> [Located a] -> R ()
+p_forallBndrs ForAllInvis _ [] = txt "forall."
+p_forallBndrs ForAllVis _ [] = txt "forall ->"
 p_forallBndrs vis p tyvars =
   switchLayout (getLoc <$> tyvars) $ do
     txt "forall"
@@ -217,8 +251,8 @@
     inci $ do
       sitcc $ sep breakpoint (sitcc . located' p) tyvars
       case vis of
-        ForallInvis -> txt "."
-        ForallVis -> space >> txt "->"
+        ForAllInvis -> txt "."
+        ForAllVis -> space >> txt "->"
 
 p_conDeclFields :: [LConDeclField GhcPs] -> R ()
 p_conDeclFields xs =
@@ -236,7 +270,6 @@
   txt "::"
   breakpoint
   sitcc . inci $ p_hsType (unLoc cd_fld_type)
-p_conDeclField (XConDeclField x) = noExtCon x
 
 tyOpTree :: LHsType GhcPs -> OpTree (LHsType GhcPs) (Located RdrName)
 tyOpTree (L _ (HsOpTy NoExtField l op r)) =
@@ -254,18 +287,25 @@
     space
     p_tyOpTree r
 
+p_lhsTypeArg :: LHsTypeArg GhcPs -> R ()
+p_lhsTypeArg = \case
+  HsValArg 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
 
 tyVarsToTypes :: LHsQTyVars GhcPs -> [LHsType GhcPs]
-tyVarsToTypes = \case
-  HsQTvs {..} -> fmap tyVarToType <$> hsq_explicit
-  XLHsQTyVars x -> noExtCon x
+tyVarsToTypes HsQTvs {..} = fmap tyVarToType <$> hsq_explicit
 
-tyVarToType :: HsTyVarBndr GhcPs -> HsType GhcPs
+tyVarToType :: HsTyVarBndr () GhcPs -> HsType GhcPs
 tyVarToType = \case
-  UserTyVar NoExtField tvar -> HsTyVar NoExtField NotPromoted tvar
-  KindedTyVar NoExtField tvar kind ->
+  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
@@ -273,4 +313,6 @@
     -- declarations.
     HsParTy NoExtField . noLoc $
       HsKindSig NoExtField (noLoc (HsTyVar NoExtField NotPromoted tvar)) kind
-  XTyVarBndr x -> noExtCon x
+
+tyVarsToTyPats :: LHsQTyVars GhcPs -> HsTyPats GhcPs
+tyVarsToTyPats HsQTvs {..} = HsValArg . fmap tyVarToType <$> hsq_explicit
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
@@ -15,8 +15,10 @@
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as M
 import Data.Maybe (fromMaybe, mapMaybe)
-import GHC
-import OccName (occNameString)
+import GHC.Types.Basic
+import GHC.Types.Name.Occurrence (occNameString)
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
 import Ormolu.Utils (unSrcSpan)
 
 -- | Intermediate representation of operator trees. It has two type
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
@@ -14,7 +14,7 @@
 import Data.Generics (everything, ext2Q)
 import Data.List (sortOn)
 import Data.Typeable (cast)
-import SrcLoc
+import GHC.Types.SrcLoc
 
 -- | A stream of 'RealSrcSpan's in ascending order. This allows us to tell
 -- e.g. whether there is another \"located\" element of AST between current
@@ -43,4 +43,4 @@
       case cast mspn :: Maybe SrcSpan of
         Nothing -> mempty
         Just (UnhelpfulSpan _) -> mempty
-        Just (RealSrcSpan spn) -> D.singleton spn
+        Just (RealSrcSpan spn _) -> D.singleton spn
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,72 @@
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
 
 -- | Common definitions for pre- and post- processing.
 module Ormolu.Processing.Common
-  ( OrmoluState (..),
-    startDisabling,
-    endDisabling,
+  ( removeIndentation,
+    reindent,
+    linesInRegion,
+    regionInbetween,
+    intSetToRegions,
   )
 where
 
-import Data.String (IsString (..))
+import Data.Char (isSpace)
+import Data.IntSet (IntSet)
+import qualified Data.IntSet as IntSet
+import Data.Text (Text)
+import qualified Data.Text as T
+import Ormolu.Config
 
--- | Ormolu state.
-data OrmoluState
-  = -- | Enabled
-    OrmoluEnabled
-  | -- | Disabled
-    OrmoluDisabled
-  deriving (Eq, Show)
+-- | 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)
+  where
+    n = minimum (getIndent <$> xs)
+    getIndent y =
+      if all isSpace y
+        then 0
+        else length (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 -> String -> String
+linesInRegion RegionDeltas {..} (lines -> ls) = unlines middle
+  where
+    (_, nonPrefix) = splitAt regionPrefixLength ls
+    middle = take (length nonPrefix - regionSuffixLength) nonPrefix
+
+-- | Get the region after the end of the first and till the end of
+-- the second region.
+regionInbetween ::
+  -- | Total number of lines
+  Int ->
+  RegionDeltas ->
+  RegionDeltas ->
+  RegionDeltas
+regionInbetween total r r' =
+  RegionDeltas
+    { regionPrefixLength = total - regionSuffixLength r,
+      regionSuffixLength = total - regionPrefixLength r'
+    }
+
+-- | 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,61 @@
 
 -- | Support for CPP.
 module Ormolu.Processing.Cpp
-  ( State (..),
-    processLine,
-    unmaskLine,
+  ( cppLines,
   )
 where
 
-import Control.Monad
 import Data.Char (isSpace)
+import Data.IntSet (IntSet)
+import qualified Data.IntSet as IntSet
 import qualified Data.List as L
 import Data.Maybe (isJust)
-import Data.String
-import Data.Text (Text)
-import qualified Data.Text 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 :: String -> IntSet
+cppLines input = IntSet.fromAscList $ go Outside (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'
-
--- | Mask prefix for CPP.
-maskPrefix :: IsString s => s
-maskPrefix = "-- ORMOLU_CPP_MASK"
+    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 <- dropWhile isSpace <$> L.stripPrefix "#" line
+          L.stripPrefix directive s
+        contState =
+          if "\\" `L.isSuffixOf` line && not inConditional
+            then InContinuation
+            else Outside
+          where
+            inConditional = case state of
+              InConditional {} -> True
+              _ -> False
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,6 @@
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Preprocessing for input source code.
@@ -9,139 +10,168 @@
 where
 
 import Control.Monad
+import Data.Bifunctor (bimap)
 import Data.Char (isSpace)
+import Data.Function ((&))
+import Data.IntMap (IntMap)
+import qualified Data.IntMap.Strict as IntMap
+import Data.IntSet (IntSet)
+import qualified Data.IntSet as IntSet
 import qualified Data.List as L
-import Data.Maybe (isJust, maybeToList)
-import FastString
+import Data.Maybe (isJust)
+import Data.Text (Text)
+import qualified Data.Text as T
 import Ormolu.Config (RegionDeltas (..))
-import Ormolu.Parser.Shebang (isShebang)
 import Ormolu.Processing.Common
-import qualified Ormolu.Processing.Cpp as Cpp
-import SrcLoc
+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, [Located String])
-preprocess path input RegionDeltas {..} =
-  go 1 OrmoluEnabled Cpp.Outside id id regionLines
+  String ->
+  [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 = T.pack . flip linesInRegion updatedInput <$> regionsNotToFormat
+      where
+        updatedInput = unlines . fmap updateLine . zip [1 ..] . 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 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 (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
+    rawLines = lines rawInput
+    rawLineLength = length rawLines
+
+    interleave [] bs = bs
+    interleave (a : as) bs = a : interleave bs as
+
+    xs !!? i = if i >= 0 && i < length xs then Just $ xs !! 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 ->
   String ->
-  -- | Adjusted line and possibly a comment extracted from it
-  (String, OrmoluState, Cpp.State, Maybe (Located String))
-processLine path n ormoluState Cpp.Outside line
-  | "{-# LINE" `L.isPrefixOf` line =
-    let (pragma, res) = getPragma line
-        size = length pragma
-        ss = mkSrcSpan (mkSrcLoc' 1) (mkSrcLoc' (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 = mkSrcSpan (mkSrcLoc' 1) (mkSrcLoc' (length line))
-     in ("", ormoluState, Cpp.Outside, Just (L ss line))
-  | otherwise =
-    let (line', cppState') = Cpp.processLine line Cpp.Outside
-     in (line', ormoluState, cppState', Nothing)
+  (IntSet, IntMap String)
+linesNotToFormat cppEnabled region@RegionDeltas {..} input =
+  (unconsidered <> magicDisabled <> otherDisabled, lineUpdates)
   where
-    mkSrcLoc' = mkSrcLoc (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 (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 :: String -> (IntSet, IntMap String)
+magicDisabledLines input =
+  bimap IntSet.fromAscList IntMap.fromAscList . mconcat $
+    go OrmoluEnabled (lines input `zip` [1 ..])
+  where
+    go _ [] = []
+    go state ((line, i) : ls)
+      | Just marker <- disablingMagicComment line,
+        state == OrmoluEnabled =
+        ([i], [(i, marker)]) : go OrmoluDisabled ls
+      | Just marker <- enablingMagicComment line,
+        state == OrmoluDisabled =
+        ([i], [(i, marker)]) : 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 :: (String -> Bool) -> String -> IntSet
+linesFiltered p =
+  IntSet.fromAscList . fmap snd . filter (p . fst) . (`zip` [1 ..]) . lines
 
--- | Return 'True' if the given string is an enabling marker.
-isOrmoluEnable :: String -> Bool
-isOrmoluEnable s = magicComment "ORMOLU_ENABLE" s || magicComment "FOURMOLU_ENABLE" s
+-- | Lines which contain a shebang.
+shebangLines :: String -> IntSet
+shebangLines = linesFiltered ("#!" `L.isPrefixOf`)
 
--- | Return 'True' if the given string is a disabling marker.
-isOrmoluDisable :: String -> Bool
-isOrmoluDisable s = magicComment "ORMOLU_DISABLE" s || magicComment "FOURMOLU_DISABLE" s
+-- | Lines which contain a LINE pragma.
+linePragmaLines :: String -> IntSet
+linePragmaLines = linesFiltered ("{-# LINE" `L.isPrefixOf`)
 
+-- | If the given string is an enabling marker (Ormolu or Fourmolu style), then
+-- return 'Just' the enabling marker. Otherwise return 'Nothing'.
+enablingMagicComment :: String -> Maybe String
+enablingMagicComment s
+  | isMagicComment "ORMOLU_ENABLE" s = Just "{- ORMOLU_ENABLE -}"
+  | isMagicComment "FOURMOLU_ENABLE" s = Just "{- FOURMOLU_ENABLE -}"
+  | otherwise = Nothing
+
+-- | If the given string is a disabling marker (Ormolu or Fourmolu style), then
+-- return 'Just' the disabling marker. Otherwise return 'Nothing'.
+disablingMagicComment :: String -> Maybe String
+disablingMagicComment s
+  | isMagicComment "ORMOLU_DISABLE" s = Just "{- ORMOLU_DISABLE -}"
+  | isMagicComment "FOURMOLU_DISABLE" s = Just "{- FOURMOLU_DISABLE -}"
+  | otherwise = Nothing
+
 -- | Construct a function for whitespace-insensitive matching of string.
-magicComment ::
+isMagicComment ::
   -- | What to expect
   String ->
   -- | String to test
   String ->
   -- | Whether or not the two strings watch
   Bool
-magicComment expected s0 = isJust $ do
+isMagicComment expected s0 = isJust $ do
   let trim = dropWhile isSpace
   s1 <- trim <$> L.stripPrefix "{-" (trim s0)
   s2 <- trim <$> L.stripPrefix expected s1
diff --git a/src/Ormolu/Terminal.hs b/src/Ormolu/Terminal.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Terminal.hs
@@ -0,0 +1,125 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | An abstraction for colorful output in terminal.
+module Ormolu.Terminal
+  ( -- * The 'Term' monad
+    Term,
+    ColorMode (..),
+    runTerm,
+
+    -- * Styling
+    bold,
+    cyan,
+    green,
+    red,
+
+    -- * Printing
+    put,
+    putS,
+    putSrcSpan,
+    newline,
+  )
+where
+
+import Control.Monad.Reader
+import Data.Text (Text)
+import qualified Data.Text.IO as T
+import GHC.Types.SrcLoc
+import Ormolu.Utils (showOutputable)
+import System.Console.ANSI
+import System.IO (Handle, hFlush, hPutStr)
+
+----------------------------------------------------------------------------
+-- The 'Term' monad
+
+-- | Terminal monad.
+newtype Term a = Term (ReaderT RC IO a)
+  deriving (Functor, Applicative, Monad)
+
+-- | Reader context of 'Term'.
+data RC = RC
+  { -- | Whether to use colors
+    rcUseColor :: Bool,
+    -- | Handle to print to
+    rcHandle :: Handle
+  }
+
+-- | Whether to use colors and other features of ANSI terminals.
+data ColorMode = Never | Always | Auto
+  deriving (Eq, Show, Enum, Bounded)
+
+-- | Run 'Term' monad.
+runTerm ::
+  -- | Monad to run
+  Term a ->
+  -- | Color mode
+  ColorMode ->
+  -- | Handle to print to
+  Handle ->
+  IO a
+runTerm (Term m) colorMode rcHandle = do
+  rcUseColor <- case colorMode of
+    Never -> return False
+    Always -> return True
+    Auto -> hSupportsANSI rcHandle
+  x <- runReaderT m RC {..}
+  hFlush rcHandle
+  return x
+
+----------------------------------------------------------------------------
+-- 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 inner computation output green text.
+green :: Term a -> Term a
+green = withSGR [SetColor Foreground Dull Green]
+
+-- | Make the inner computation output red text.
+red :: Term a -> Term a
+red = withSGR [SetColor Foreground Dull Red]
+
+-- | 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
+
+----------------------------------------------------------------------------
+-- Printing
+
+-- | Output 'Text'.
+put :: Text -> Term ()
+put txt = Term $ do
+  RC {..} <- ask
+  liftIO $ T.hPutStr rcHandle txt
+
+-- | Output 'String'.
+putS :: String -> Term ()
+putS str = Term $ do
+  RC {..} <- ask
+  liftIO $ hPutStr rcHandle str
+
+-- | Output a 'GHC.SrcSpan'.
+putSrcSpan :: SrcSpan -> Term ()
+putSrcSpan = putS . showOutputable
+
+-- | Output a newline.
+newline :: Term ()
+newline = Term $ do
+  RC {..} <- ask
+  liftIO $ T.hPutStr rcHandle "\n"
diff --git a/src/Ormolu/Utils.hs b/src/Ormolu/Utils.hs
--- a/src/Ormolu/Utils.hs
+++ b/src/Ormolu/Utils.hs
@@ -10,27 +10,25 @@
     notImplemented,
     showOutputable,
     splitDocString,
-    typeArgToType,
     unSrcSpan,
     incSpanLine,
     separatedByBlank,
     separatedByBlankNE,
     onTheSameLine,
-    removeIndentation,
     groupBy',
   )
 where
 
-import Data.Char (isSpace)
 import Data.List (dropWhileEnd)
-import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
+import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe (fromMaybe)
 import Data.Text (Text)
 import qualified Data.Text as T
-import GHC
 import GHC.DynFlags (baseDynFlags)
-import qualified Outputable as GHC
+import GHC.Hs
+import GHC.Types.SrcLoc
+import qualified GHC.Utils.Outputable as GHC
 
 -- | Relative positions in a list.
 data RelativePos
@@ -86,23 +84,16 @@
         _ -> txt
     escapeCommentBraces = T.replace "{-" "{\\-" . T.replace "-}" "-\\}"
 
--- | Get 'LHsType' out of 'LHsTypeArg'.
-typeArgToType :: LHsTypeArg p -> LHsType p
-typeArgToType = \case
-  HsValArg tm -> tm
-  HsTypeArg _ ty -> ty
-  HsArgPar _ -> notImplemented "HsArgPar"
-
 -- | Get 'RealSrcSpan' out of 'SrcSpan' if the span is “helpful”.
 unSrcSpan :: SrcSpan -> Maybe RealSrcSpan
 unSrcSpan = \case
-  RealSrcSpan r -> Just r
+  RealSrcSpan r _ -> Just r
   UnhelpfulSpan _ -> Nothing
 
 -- | Increment line number in a 'SrcSpan'.
 incSpanLine :: Int -> SrcSpan -> SrcSpan
 incSpanLine i = \case
-  RealSrcSpan s ->
+  RealSrcSpan s _ ->
     let start = realSrcSpanStart s
         end = realSrcSpanEnd s
         incLine x =
@@ -110,7 +101,7 @@
               line = srcLocLine x
               col = srcLocCol x
            in mkRealSrcLoc file (line + i) col
-     in RealSrcSpan (mkRealSrcSpan (incLine start) (incLine end))
+     in RealSrcSpan (mkRealSrcSpan (incLine start) (incLine end)) Nothing
   UnhelpfulSpan x -> UnhelpfulSpan x
 
 -- | Do two declarations have a blank between them?
@@ -129,19 +120,6 @@
 onTheSameLine :: SrcSpan -> SrcSpan -> Bool
 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)
-  where
-    n = case nonEmpty xs of
-      Nothing -> 0
-      Just l -> minimum (getIndent <$> l)
-    getIndent y =
-      if all isSpace y
-        then 0
-        else length (takeWhile isSpace y)
 
 -- | A generalisation of 'groupBy' to functions which aren't equivalences - a group ends
 -- when comparison fails with the previous element, rather than the first of the group.
diff --git a/src/Ormolu/Utils/Extensions.hs b/src/Ormolu/Utils/Extensions.hs
new file mode 100644
--- /dev/null
+++ b/src/Ormolu/Utils/Extensions.hs
@@ -0,0 +1,145 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Ormolu.Utils.Extensions
+  ( Extension (..),
+    getExtensionsFromCabalFile,
+    findCabalFile,
+    getCabalExtensionDynOptions,
+  )
+where
+
+import Control.Exception
+import Control.Monad.IO.Class
+import qualified Data.ByteString as B
+import Data.Map.Lazy (Map)
+import qualified Data.Map.Lazy as M
+import Data.Maybe (maybeToList)
+import qualified Distribution.ModuleName as ModuleName
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parsec
+import qualified Distribution.Types.CondTree as CT
+import Language.Haskell.Extension
+import Ormolu.Config
+import Ormolu.Exception
+import System.Directory
+import System.FilePath
+import System.IO (hPutStrLn, stderr)
+import System.IO.Error (isDoesNotExistError)
+
+-- | Get a map from Haskell source file paths (without any extensions)
+-- to its default language extensions
+getExtensionsFromCabalFile ::
+  MonadIO m =>
+  -- | Path to cabal file
+  FilePath ->
+  m (Map FilePath [DynOption])
+getExtensionsFromCabalFile cabalFile = liftIO $ do
+  GenericPackageDescription {..} <-
+    parseGenericPackageDescriptionMaybe <$> B.readFile cabalFile >>= \case
+      Just gpd -> pure gpd
+      Nothing -> throwIO $ OrmoluCabalFileParsingFailed cabalFile
+  let lib = maybeToList condLibrary
+      sublibs = snd <$> condSubLibraries
+  pure . M.unions . concat $
+    [ buildMap extractFromLibrary <$> lib ++ sublibs,
+      buildMap extractFromExecutable . snd <$> condExecutables,
+      buildMap extractFromTestSuite . snd <$> condTestSuites,
+      buildMap extractFromBenchmark . snd <$> condBenchmarks
+    ]
+  where
+    buildMap f a = let (files, exts) = f mergedA in M.fromList $ (,exts) <$> files
+      where
+        (mergedA, _) = CT.ignoreConditions a
+
+    extractFromBuildInfo extraModules BuildInfo {..} = (,exts) $ do
+      m <- extraModules ++ (ModuleName.toFilePath <$> otherModules)
+      (takeDirectory cabalFile </>) <$> prependSrcDirs (dropExtensions m)
+      where
+        prependSrcDirs f
+          | null hsSourceDirs = [f]
+          | otherwise = (</> f) <$> hsSourceDirs
+        exts = maybe [] langExt defaultLanguage ++ fmap extToDynOption defaultExtensions
+        langExt =
+          pure . DynOption . \case
+            Haskell98 -> "-XHaskell98"
+            Haskell2010 -> "-XHaskell2010"
+            UnknownLanguage lan -> "-X" ++ 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 [modulePath] buildInfo
+    extractFromTestSuite TestSuite {..} =
+      extractFromBuildInfo mainPath testBuildInfo
+      where
+        mainPath = case testInterface of
+          TestSuiteExeV10 _ p -> [p]
+          TestSuiteLibV09 _ p -> [ModuleName.toFilePath p]
+          TestSuiteUnsupported {} -> []
+    extractFromBenchmark Benchmark {..} =
+      extractFromBuildInfo mainPath benchmarkBuildInfo
+      where
+        mainPath = case benchmarkInterface of
+          BenchmarkExeV10 _ p -> [p]
+          BenchmarkUnsupported {} -> []
+
+-- | Find the path to an appropriate .cabal file for a Haskell
+-- source file, if available
+findCabalFile ::
+  MonadIO m =>
+  -- | Absolute path to a Haskell source file in a project with a .cabal file
+  FilePath ->
+  m (Maybe FilePath)
+findCabalFile p = liftIO $ do
+  let parentDir = takeDirectory p
+  dirEntries <-
+    listDirectory parentDir `catch` \case
+      (isDoesNotExistError -> True) -> pure []
+      e -> throwIO e
+  let findDotCabal = \case
+        [] -> pure Nothing
+        e : es
+          | takeExtension e == ".cabal" ->
+            doesFileExist (parentDir </> e) >>= \case
+              True -> pure $ Just e
+              False -> findDotCabal es
+        _ : es -> findDotCabal es
+  findDotCabal dirEntries >>= \case
+    Just cabalFile -> pure . Just $ parentDir </> cabalFile
+    Nothing ->
+      if isDrive parentDir
+        then pure Nothing
+        else findCabalFile parentDir
+
+-- | Get the default language extensions of a Haskell source file.
+-- The .cabal file can be provided explicitly or auto-detected.
+getCabalExtensionDynOptions ::
+  MonadIO m =>
+  -- | Haskell source file
+  FilePath ->
+  m [DynOption]
+getCabalExtensionDynOptions sourceFile' = liftIO $ do
+  sourceFile <- makeAbsolute sourceFile'
+  findCabalFile sourceFile >>= \case
+    Just cabalFile -> do
+      extsByFile <- getExtensionsFromCabalFile cabalFile
+      case M.lookup (dropExtensions sourceFile) extsByFile of
+        Just exts -> pure exts
+        Nothing -> do
+          relativeCabalFile <- makeRelativeToCurrentDirectory cabalFile
+          note $
+            "Found .cabal file "
+              <> relativeCabalFile
+              <> ", but it did not mention "
+              <> sourceFile'
+    Nothing -> note $ "Could not find a .cabal file for " <> sourceFile'
+  where
+    note msg = [] <$ hPutStrLn stderr msg
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,23 @@
+-- | Write 'Text' to files using UTF8 and ignoring native
+-- line ending conventions.
+module Ormolu.Utils.IO
+  ( writeFileUtf8,
+    readFileUtf8,
+  )
+where
+
+import Control.Exception (throwIO)
+import Control.Monad.IO.Class
+import qualified Data.ByteString as B
+import Data.Text (Text)
+import qualified Data.Text.Encoding as TE
+
+-- | Write a 'Text' to a file using UTF8 and ignoring native
+-- line ending conventions.
+writeFileUtf8 :: MonadIO m => FilePath -> Text -> m ()
+writeFileUtf8 p = liftIO . B.writeFile p . TE.encodeUtf8
+
+-- | Read an entire file strictly into a 'Text' using UTF8 and
+-- ignoring native line ending conventions.
+readFileUtf8 :: MonadIO m => FilePath -> m Text
+readFileUtf8 p = liftIO $ either throwIO pure . TE.decodeUtf8' =<< B.readFile p
diff --git a/tests/Ormolu/CabalExtensionsSpec.hs b/tests/Ormolu/CabalExtensionsSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/CabalExtensionsSpec.hs
@@ -0,0 +1,35 @@
+module Ormolu.CabalExtensionsSpec (spec) where
+
+import qualified Data.Map as M
+import Ormolu.Config
+import Ormolu.Utils.Extensions
+import System.Directory
+import System.FilePath
+import System.IO.Temp (withSystemTempDirectory)
+import Test.Hspec
+
+spec :: Spec
+spec = describe "Handle extensions from .cabal files" $ do
+  it "extract extensions from .cabal files" $ do
+    extsByFile <- getExtensionsFromCabalFile "fourmolu.cabal"
+    extsByFile `shouldNotSatisfy` M.null
+    M.elems extsByFile `shouldSatisfy` all (== [DynOption "-XHaskell2010"])
+    extsByFile
+      `shouldSatisfy` members
+        [ "./src/Ormolu",
+          "./app/Main",
+          "./tests/Spec"
+        ]
+  it "find the .cabal file" $ do
+    let findsOrmoluCabal start expectedCabalFile = do
+          cabalFile <- findCabalFile start
+          cabalFile `shouldBe` Just expectedCabalFile
+    findsOrmoluCabal "src/Ormolu/Config.hs" "./fourmolu.cabal"
+    findsOrmoluCabal "a/b/c/d/e" "./fourmolu.cabal"
+  it "do not consider directories as .cabal files" $
+    withSystemTempDirectory "" $ \dir -> do
+      createDirectory $ dir </> ".cabal"
+      cabalFile <- findCabalFile $ dir </> "foo/bar.hs"
+      cabalFile `shouldBe` Nothing
+  where
+    members as m = all (`M.member` m) as
diff --git a/tests/Ormolu/Diff/TextSpec.hs b/tests/Ormolu/Diff/TextSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/Diff/TextSpec.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Ormolu.Diff.TextSpec (spec) where
+
+import Data.Text (Text)
+import Ormolu.Diff.Text
+import Ormolu.Terminal
+import Ormolu.Utils.IO
+import Path
+import Path.IO
+import qualified System.FilePath as FP
+import System.IO (hClose)
+import Test.Hspec
+
+spec :: Spec
+spec =
+  describe "printTextDiff" $ do
+    stdTest "one-line-added" "empty" "one-line"
+    stdTest "one-line-removed" "one-line" "empty"
+    stdTest "no-preceding" "main-foo" "main"
+    stdTest "no-following" "main" "main-v2"
+    stdTest "simple-hunk" "main-and-foo" "main-and-foo-v2"
+    stdTest "joined-hunk" "main-and-bar" "main-and-bar-v2"
+    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"
+
+-- | Test diff printig.
+stdTest ::
+  -- | Name of the test case
+  String ->
+  -- | Location of input A
+  FilePath ->
+  -- | Location of input B
+  FilePath ->
+  Spec
+stdTest name pathA pathB = it name $ do
+  inputA <-
+    parseRelFile (FP.addExtension pathA "hs")
+      >>= readFileUtf8 . toFilePath . (diffInputsDir </>)
+  inputB <-
+    parseRelFile (FP.addExtension pathB "hs")
+      >>= readFileUtf8 . toFilePath . (diffInputsDir </>)
+  let expectedDiffPath = FP.addExtension name "txt"
+  expectedDiffText <-
+    parseRelFile expectedDiffPath
+      >>= readFileUtf8 . 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
+    readFileUtf8 (toFilePath path)
+
+diffTestsDir :: Path Rel Dir
+diffTestsDir = $(mkRelDir "data/diff-tests")
+
+diffInputsDir :: Path Rel Dir
+diffInputsDir = diffTestsDir </> $(mkRelDir "inputs")
+
+diffOutputsDir :: Path Rel Dir
+diffOutputsDir = diffTestsDir </> $(mkRelDir "outputs")
diff --git a/tests/Ormolu/Parser/OptionsSpec.hs b/tests/Ormolu/Parser/OptionsSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Ormolu/Parser/OptionsSpec.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE LambdaCase #-}
+
+module Ormolu.Parser.OptionsSpec (spec) where
+
+import qualified Data.Text as T
+import Ormolu
+import Test.Hspec
+
+spec :: Spec
+spec = describe "GHC options in source files take priority" $ do
+  it "default extensions can be disabled locally" $ do
+    let src =
+          unlines
+            [ "{-# LANGUAGE NoBlockArguments #-}",
+              "",
+              "test = test do test"
+            ]
+    fixedPoint [] src `shouldThrow` \case
+      OrmoluParsingFailed {} -> True
+      _ -> False
+  it "extensions disabled via CLI can be enabled locally" $ do
+    let src =
+          unlines
+            [ "{-# LANGUAGE BlockArguments #-}",
+              "",
+              "test = test do test"
+            ]
+    fixedPoint ["-XNoBlockArguments"] src
+  where
+    fixedPoint opts input = do
+      output <- ormolu defaultConfig {cfgDynOptions = DynOption <$> opts} "<input>" input
+      T.unpack 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/PrinterSpec.hs b/tests/Ormolu/PrinterSpec.hs
--- a/tests/Ormolu/PrinterSpec.hs
+++ b/tests/Ormolu/PrinterSpec.hs
@@ -4,13 +4,12 @@
 
 import Control.Exception
 import Control.Monad
-import Control.Monad.IO.Class
 import Data.List (isSuffixOf)
 import Data.Text (Text)
 import qualified Data.Text as T
-import qualified Data.Text.IO as T
 import Ormolu
 import Ormolu.Config
+import Ormolu.Utils.IO
 import Path
 import Path.IO
 import qualified System.FilePath as F
@@ -30,11 +29,11 @@
             poHaddockStyle = pure HaddockSingleLine,
             poNewlinesBetweenDecls = pure 1
           }
-  sequence_ $ uncurry checkExample <$> [(ormoluOpts, ""), (defaultPrinterOpts, "-four")] <*> es
+  sequence_ $ checkExample <$> [(ormoluOpts, "ormolu", ""), (defaultPrinterOpts, "fourmolu", "-four")] <*> es
 
 -- | Check a single given example.
-checkExample :: PrinterOptsTotal -> String -> Path Rel File -> Spec
-checkExample po suffix srcPath' = it (fromRelFile srcPath' ++ " works") . withNiceExceptions $ do
+checkExample :: (PrinterOptsTotal, String, String) -> Path Rel File -> Spec
+checkExample (po, label, suffix) srcPath' = it (fromRelFile srcPath' ++ " works (" ++ label ++ ")") . withNiceExceptions $ do
   let srcPath = examplesDir </> srcPath'
       cfg = defaultConfig {cfgPrinterOpts = po}
   expectedOutputPath <- deriveOutput suffix srcPath
@@ -45,8 +44,9 @@
   formatted0 <- ormoluFile cfg (fromRelFile srcPath)
   -- 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
+  -- <<< UNCOMMENT NEXT LINE TO REGENERATE OUTPUT FILES >>>
+  -- writeFile (fromRelFile expectedOutputPath) (T.unpack formatted0)
+  expected <- readFileUtf8 $ fromRelFile expectedOutputPath
   shouldMatch False formatted0 expected
   -- 4. Check that running the formatter on the output produces the same
   -- output again (the transformation is idempotent).
