diff --git a/.hlint.yaml b/.hlint.yaml
--- a/.hlint.yaml
+++ b/.hlint.yaml
@@ -5,10 +5,18 @@
 # Hints that apply only to the HLint source code
 
 #####################################################################
+## GROUPS OF HINTS WE TURN ON
+
+- group: {name: future, enabled: true}
+- group: {name: extra, enabled: true}
+
+
+#####################################################################
 ## RESTRICTIONS
 
 - extensions:
   - default: false
+  - name: [ImportQualifiedPost]
   - name: [DeriveDataTypeable, DeriveFunctor, GeneralizedNewtypeDeriving, NoMonomorphismRestriction, OverloadedStrings]
   - name: [MultiWayIf, PatternGuards, RecordWildCards, ViewPatterns, PatternSynonyms, TupleSections, LambdaCase]
   - name: [Rank2Types, ScopedTypeVariables]
@@ -17,7 +25,9 @@
   - name: [PackageImports]
   - name: [ConstraintKinds, RankNTypes, TypeFamilies]
   - name: [TemplateHaskell]
+  - name: [DerivingVia, DeriveGeneric, DataKinds]
   - {name: CPP, within: [HsColour, Config.Yaml, Test.Annotations]} # so it can be disabled to avoid GPL code
+  - {name: CPP, within: CmdLine} # Don't think this one is really necessary
 
 - flags:
   - default: false
@@ -62,7 +72,7 @@
 
 # doesn't fit with the other statements
 - ignore: {name: Use let, within: [HLint, Test.All]}
-# this const has meaingful argument names
+# this const has meaningful argument names
 - ignore: {name: Use const, within: Config.Yaml}
 # TEMPORARY: this lint is deleted on HEAD
 - ignore: {name: Use String}
diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,148 @@
 Changelog for HLint (* = breaking change)
 
+3.10, released 2024-02-02
+    #1617, hints for when x <*> y could be y, e.g. []
+*   #1594, upgrade to GHC 9.12
+    #1568, add mapMaybe f (reverse x) ==> reverse (mapMaybe f x)
+    #1569, avoid redundant Foldable.toList calls in Foldable operations
+    #1571, rename a few hints to make them clearer
+    #1599, add translated 0 0 hint for CodeWorld project
+    #1600, add sum [x, y] ==> x + y
+    #1549, downgrade a few of the error severities to warn
+3.8, released 2024-01-15
+    #1552, make --git and --ignore-glob work nicely together
+    #1502, fix incorrect free variable calculation in some cases
+    #1555, slightly more efficient concatMap usages (e.g. pull filter out)
+    #1500, suggest avoiding NonEmpty.unzip (use Functor.unzip)
+*   #1544, upgrade to GHC 9.8
+    #1540, correct Functor law hint, was missing brackets
+3.6.1, released 2023-07-03
+    Attempt to make a binary release
+3.6, released 2023-06-26
+    #1522, define __GLASGOW_HASKELL__ for cmdCpp
+    #1519, don't suggest removing brackets that result in a parse error
+    #1512, add hints to convert for [1..n] to replicateM n
+    #1430, add ignore-suggestions flag
+    #1517, support NO_COLOR
+    #1505, don't suggest redundant brackets around constraints
+    #1478, Fix no warn on (x.y) by treating x.y as atom
+    #1481, more hints about concat/concatMap
+    #1485, add more hints about using a Functor on a literal tuple
+    #1479, add specialised not hints for notElem/notNull
+    #1437, don't add/remove Monad constraints in replacements
+    #1475, rules for (elem False) and (notElem True)
+    #1476, generalise map-consuming rules from lists to Foldables/Traversables
+    #1480, add hints about notElem and notNull
+    #1482, support SARIF output
+    #1470, drop support for GHC 9.0
+*   #1470, move to GHC 9.6 parser
+    #1449, add some more generalize hints that suggest fmap instead of map
+    Never suggest concatForM_, because it doesn't exist
+    #1467, and groups for warning on partial functions
+    #1465, improve the timing information with -v flag
+    #1454, filter Just . map f ==> map Just . mapMaybe f
+    #1452, add more evaluate rules, e.g. fromJust (Just x) ==> x
+    #1488, add <$>/<&> hints for Either
+    #1447, make <$> hints also work on <&> (dualized)
+    #1450, suggest nub/sort ==> nubSort with the extra hints
+    #1451, isJust y && (x == fromJust y) ==> Just x == y
+    #578, don't suggest numeric underscores for < 5 digits
+    #1428, rename "Use map" hints that introduce tuple sections
+    #1424, don't suggest sortOn; suggest avoiding `reverse . sort`
+3.5, released 2022-09-20
+    #1421, change zip/repeat to map with tuple section
+    #1418, add more QuickCheck hints
+    #1420, suggest use of Data.Tuple.Extra.both in the extra hints
+    #1407, fix some list-comp hints that applied too broadly
+    #1407, suggest [ f x | x <- [y] ] to [f y]
+    #1417, add some more isAlpha/isDigit suggestions
+    #1411, add some empty list equivalent to "" hints
+    #1416, add hints for (== True) and other bool/section values
+    #1410, remove support for building with GHC 8.10
+*   #1410, always default to using ghc-parser instead of the GHC API
+*   #1410, upgrade to the GHC 9.4 parse tree
+    #1408, evaluate calls of map with empty/singleton lists
+    #1409, add notNull hints, e.g. notNull . concat ==> any notNull
+    #1406, spot list comprehension that should be lefts/rights
+    #1404, add more if/then/else to min or max hints
+    #1403, add last . reverse ==> head
+    #1397, evaluation rules for minimum/maximum on singleton lists
+3.4.1, released 2022-07-10
+    #1345, add --generate-config to generate a complete config
+    #1345, add --generate-summary-json
+    #1377, make anyM/allM on [] produce pure, rather than return
+    #1377, add a pure rule for every return variant
+    #1380, add counts as comments for --default
+    #1372, remove unnecessary brackets when suggesting forM_
+    #1372, suggest void (forM x y) to forM_ without the void
+    #1394, replace maximum [a, b] ==> max a b (and for min)
+    #1393, for QuickCheck, join . elements ==> oneOf
+    #1387, bypass camelCase hint for new tasty_... custom test prefix
+3.4, released 2022-04-24
+    #1360, make -XHaskell2010 still obey CPP pragmas
+    #1368, make TH quotation bracket rule off by default
+    #1367, add brackets on refactoring templates when needed
+    #1348, make module restrict hints more powerful
+    #1363, add more hints for <$>
+    #1362, add support for language specifier GHC2021
+    #1342, make module wildcards work with `within` restrictions
+    #1340, include Restriction hints in splitSettings and argsSettings output
+    #1330, make ignore: {} not ignore subsequent hints
+    #1317, evaluating all/any/allM/anyM on simple lists
+    #1303, allow more matches for modules doing `import Prelude ()`
+    #1324, add createModuleExWithFixities
+    #1336, warn on unused OverloadedRecordDot
+    #1334, don't remove TypeApplications if there are type-level type applications
+    #1332, suggest using iterate instead of scanl/foldl
+    #1331, suggest using min/max instead of if
+*   #1247, move to GHC 9.2
+3.3.6, released 2021-12-29
+    #1326, produce release binaries
+3.3.5, released 2021-12-12
+    #1304, support aeson-2.0
+    #1309, suggest `either Left f x` becomes `f =<< x`
+    #1295, suggest TemplateHaskell to TemplateHaskellQuotes if it works
+    #1292, don't say redundant bracket around pattern splices
+    #1289, suggest expanding tuple sections in some cases
+    #1289, suggest length [1..n] ==> max 0 n
+    #1279, suggest using NumericUnderscores more if it is enabled
+    #1290, move reverse out of filter
+3.3.4, released 2021-08-30
+    #1288, fix generation of Linux binaries
+3.3.3, released 2021-08-29
+    #1286, compatibility with extra-1.7.10
+    #114, if OverloadedLists are enabled, don't suggest list literals
+3.3.2, released 2021-08-28
+    #1244, add `only` restriction to modules
+    #1278, make --ignore-glob patterns also ignore directories
+    #1268, move nub/sort/reverse over catMaybes/lefts/rights
+    #1276, fix some incorrect unused LANGUAGE warnings
+    #1271, suggest foldr (<>) mempty ==> fold (not mconcat)
+    #1274, make the (& f) ==> f hint apply more
+    #1264, suggest eta reduction under a where
+    #1266, suggest () <$ x ==> void x
+    #1223, add some traverse laws
+    #1254, suggest null [x] ==> False
+    #1253, suggest reverse . init ==> tail . reverse
+    #1253, suggest null . concat ==> all null
+    #1255, suggest filter instead of list comprehension in teaching
+3.3.1, released 2021-04-26
+    #1221, allow restrictions to use wildcards
+    #1225, treat A{} as not-atomic for bracket hints
+    #1233, -XHaskell98 and -XHaskell2010 now disable extensions too
+    #1226, don't warn on top-level splices with brackets
+    #1230, disable LexicalNegation by default
+    #1219, suggest uncurry f (a, b) ==> f a b
+    #1227, remove some excess brackets generated by refactoring
+3.3, released 2021-03-14
+    #1212, don't suggest redundant brackets on $(x)
+    #1215, suggest varE 'foo ==> [|foo|]
+    #1215, allow matching on Template Haskell variables
+    #1216, require apply-refact 0.9.1.0
+*   #1209, switch to the GHC 9.0.1 parse tree
+    Drop GHC 8.6 support
+    #1206, require apply-refact 0.9.0.0 or higher
+    #1205, generalize hints which were with '&' form
 3.2.8, released 2021-12-27
     #1304, support aeson-2.0
     #1286, compatibility with extra-1.7.10
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Neil Mitchell 2006-2021.
+Copyright Neil Mitchell 2006-2025.
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# HLint [![Hackage version](https://img.shields.io/hackage/v/hlint.svg?label=Hackage)](https://hackage.haskell.org/package/hlint) [![Stackage version](https://www.stackage.org/package/hlint/badge/nightly?label=Stackage)](https://www.stackage.org/package/hlint) [![Build status](https://img.shields.io/github/workflow/status/ndmitchell/hlint/ci.svg)](https://github.com/ndmitchell/hlint/actions)
+# HLint [![Hackage version](https://img.shields.io/hackage/v/hlint.svg?label=Hackage)](https://hackage.haskell.org/package/hlint) [![Stackage version](https://www.stackage.org/package/hlint/badge/nightly?label=Stackage)](https://www.stackage.org/package/hlint) [![Build status](https://img.shields.io/github/actions/workflow/status/ndmitchell/hlint/ci.yml?branch=master)](https://github.com/ndmitchell/hlint/actions)
 
 HLint is a tool for suggesting possible improvements to Haskell code. These suggestions include ideas such as using alternative functions, simplifying code and spotting redundancies. This document is structured as follows:
 
@@ -13,13 +13,15 @@
 
 * HLint operates on each module at a time in isolation, as a result HLint does not know about types or which names are in scope. This decision is deliberate, allowing HLint to parallelise and be used incrementally on code that may not type-check. If fixities are required to parse the code properly, they [can be supplied](./README.md#why-doesnt-hlint-know-the-fixity-for-my-custom--operator).
 * The presence of `seq` may cause some hints (i.e. eta-reduction) to change the semantics of a program.
-* Some transformed programs may require additional type signatures, particularly if the transformations trigger the monomorphism restriction or involve rank-2 types.
+* Some transformed programs may require [additional type signatures](https://stackoverflow.com/questions/16402942/how-can-eta-reduction-of-a-well-typed-function-result-in-a-type-error/), particularly if the transformations trigger the monomorphism restriction or involve rank-2 types. In rare cases, there might be [nowhere to write](https://stackoverflow.com/questions/19758828/eta-reduce-is-not-always-held-in-haskell) the required type signature.
 * Sometimes HLint will change the code in a way that causes values to default to different types, which may change the behaviour.
 * HLint assumes duplicate identical expressions within in a single expression are used at the same type.
 * The `RebindableSyntax` extension can cause HLint to suggest incorrect changes.
 * HLint can be configured with knowledge of C Pre Processor flags, but it can only see one conditional set of code at a time.
 * HLint turns on many language extensions so it can parse more documents, occasionally some break otherwise legal syntax - e.g. `{-#INLINE foo#-}` doesn't work with `MagicHash`, `foo $bar` means something different with `TemplateHaskell`. These extensions can be disabled with `-XNoMagicHash` or `-XNoTemplateHaskell` etc.
 * HLint doesn't run any custom preprocessors, e.g. [markdown-unlit](https://hackage.haskell.org/package/markdown-unlit) or [record-dot-preprocessor](https://hackage.haskell.org/package/record-dot-preprocessor), so code making use of them will usually fail to parse.
+* Some hints, like `Use const`, don't work for non-lifted (i.e. unlifted and unboxed) types.
+* Some language extensions like `Strict` can cause certain hints (e.g. eta reduction) to be incorrect.
 
 ## Installing and running HLint
 
@@ -55,7 +57,7 @@
 
 The first hint is marked as an warning, because using `concatMap` in preference to the two separate functions is always desirable. In contrast, the removal of brackets is probably a good idea, but not always. Reasons that a hint might be a suggestion include requiring an additional import, something not everyone agrees on, and functions only available in more recent versions of the base library.
 
-Any configuration can be done via [.hlint.yaml](./README.md#customizing-the-hints) file.
+Any configuration can be done via [.hlint.yaml](./README.md#customizing-the-hints) file. Any other file name, such as `.hlint.yml`, can only be used explicitly with the `--hint=FILE` option.
 
 **Bug reports:** The suggested replacement should be equivalent - please report all incorrect suggestions not mentioned as known limitations.
 
@@ -95,6 +97,8 @@
 * [hlint-test](https://hackage.haskell.org/package/hlint-test) helps you write a small test runner with HLint.
 * [hint-man](https://github.com/apps/hint-man) automatically submits reviews to opened pull requests in your repositories with inline hints.
 * [CircleCI](https://circleci.com/orbs/registry/orb/haskell-works/hlint) has a plugin to run HLint more easily.
+* [haskell/actions](https://github.com/haskell/actions) has `hlint-setup` and `hlint-run` actions for GitHub.
+* [haskell-actions/hlint-scan](https://github.com/haskell-actions/hlint-scan) is a GitHub action using HLint for [code scanning](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/about-code-scanning).
 
 ### Automatically Applying Hints
 
@@ -200,6 +204,25 @@
 
 HLint uses the `hlint.yaml` file it ships with by default (containing things like the `concatMap` hint above), along with the first `.hlint.yaml` file it finds in the current directory or any parent thereof. To include other hints, pass `--hint=filename.yaml`.
 
+#### Are there any extra hints available?
+
+There are a few groups of hints that are shipped with HLint, but disabled by default. These are:
+
+* `future`, which suggests switching `return` for `pure`.
+* `extra`, which suggests replacements which introduce a dependency on the [`extra` library](https://hackage.haskell.org/package/extra).
+* `use-lens`, which suggests replacements which introduce a dependency on the [`lens` library](https://hackage.haskell.org/package/lens).
+* `use-th-quotes`, which suggests using `[| x |]` where possible.
+* `generalise`, which suggests more generic methods, e.g. `fmap` instead of `map`.
+* `generalise-for-conciseness`, which suggests more generic methods, but only when they are shorter, e.g. `maybe True` becomes `all`.
+* `dollar` which suggests `a $ b $ c` is replaced with `a . b $ c`.
+* `teaching` which encourages a simple beginner friendly style, learning about related functions.
+
+These can be enabled by passing `--with-group=future` or adding the following to your `.hlint.yaml` file:
+
+```yaml
+- group: {name: future, enabled: true}
+```
+
 ### Design
 
 #### Why are hints not applied recursively?
@@ -246,15 +269,21 @@
 hlint --default > .hlint.yaml
 ```
 
-This default configuration contains lots of examples, including:
+This default configuration shows lots of examples (as `# comments`) of how to:
 
-* Adding command line arguments to all runs, e.g. `--color` or `-XNoMagicHash`.
-* Ignoring certain hints, perhaps within certain modules/functions.
-* Restricting use of GHC flags/extensions/functions, e.g. banning `Arrows` and `unsafePerformIO`.
-* Adding additional project-specific hints.
+* Add command line arguments to all runs, e.g. `--color` or `-XNoMagicHash`.
+* Ignore certain hints, perhaps within certain modules/functions.
+* Restrict the use of GHC flags/extensions/functions, e.g. banning `Arrows` and `unsafePerformIO`.
+* Add additional project-specific hints.
 
-You can see the output of `--default` [here](https://github.com/ndmitchell/hlint/blob/master/data/default.yaml).
+You can see the output of `--default` for a clean lint [here](https://github.com/ndmitchell/hlint/blob/master/data/default.yaml) but for a dirty project `--default` output includes an extra warnings section that counts and ignores any hints it finds:
 
+  ```yaml
+  # Warnings currently triggered by your code
+  - ignore: {name: "Redundant $"} # 20 hints
+  - ignore: {name: "Unused LANGUAGE pragma"} # 29 hints
+  ```
+
 If you wish to use the [Dhall configuration language](https://github.com/dhall-lang/dhall-lang) to customize HLint, there [is an example](https://kowainik.github.io/posts/2018-09-09-dhall-to-hlint) and [type definition](https://github.com/kowainik/relude/blob/master/hlint/Rule.dhall).
 
 ### Finding the name of a hint
@@ -281,11 +310,11 @@
 
 * `{-# ANN module "HLint: ignore" #-}` or `{-# HLINT ignore #-}` or `{- HLINT ignore -}` - ignore all hints in this module (use `module` literally, not the name of the module).
 * `{-# ANN module "HLint: ignore Eta reduce" #-}` or `{-# HLINT ignore "Eta reduce" #-}` or `{- HLINT ignore "Eta reduce" -}` - ignore all eta reduction suggestions in this module.
-* `{-# ANN myFunction "HLint: ignore" #-}` or `{-# HLINT ignore myFunction #-}` or `{- HLINT ignore myFunction -}` - don't give any hints in the function `myFunction`.
-* `{-# ANN myFunction "HLint: error" #-}` or `{-# HLINT error myFunction #-}` or `{- HLINT error myFunction -}` - any hint in the function `myFunction` is an error.
+* `{-# ANN myDef "HLint: ignore" #-}` or `{-# HLINT ignore myDef #-}` or `{- HLINT ignore myDef -}` - don't give any hints in the definition `myDef`. This may be combined with hint names, `{- HLINT ignore myDef "Eta reduce" -}`, to only ignore that hint in that definition.
+* `{-# ANN myDef "HLint: error" #-}` or `{-# HLINT error myDef #-}` or `{- HLINT error myDef -}` - any hint in the definition `myDef` is an error.
 * `{-# ANN module "HLint: error Use concatMap" #-}` or `{-# HLINT error "Use concatMap" #-}` or `{- HLINT error "Use concatMap" -}` - the hint to use `concatMap` is an error (you may also use `warn` or `suggest` in place of `error` for other severity levels).
 
-For `ANN` pragmas it is important to put them _after_ any `import` statements. If you have the `OverloadedStrings` extension enabled you will need to give an explicit type to the annotation, e.g. `{-# ANN myFunction ("HLint: ignore" :: String) #-}`. The `ANN` pragmas can also increase compile times or cause more recompilation than otherwise required, since they are evaluated by `TemplateHaskell`.
+For `ANN` pragmas it is important to put them _after_ any `import` statements. If you have the `OverloadedStrings` extension enabled you will need to give an explicit type to the annotation, e.g. `{-# ANN myDef ("HLint: ignore" :: String) #-}`. The `ANN` pragmas can also increase compile times or cause more recompilation than otherwise required, since they are evaluated by `TemplateHaskell`.
 
 For `{-# HLINT #-}` pragmas GHC may give a warning about an unrecognised pragma, which can be suppressed with `-Wno-unrecognised-pragmas`.
 
@@ -295,8 +324,8 @@
 
 * `- ignore: {name: Eta reduce}` - suppress all eta reduction suggestions.
 * `- ignore: {name: Eta reduce, within: [MyModule1, MyModule2]}` - suppress eta reduction hints in the `MyModule1` and `MyModule2` modules.
-* `- ignore: {within: MyModule.myFunction}` - don't give any hints in the function `MyModule.myFunction`.
-* `- error: {within: MyModule.myFunction}` - any hint in the function `MyModule.myFunction` is an error.
+* `- ignore: {within: MyModule.myDef}` - don't give any hints in the definition `MyModule.myDef`.
+* `- error: {within: MyModule.myDef}` - any hint in the definition `MyModule.myDef` is an error.
 * `- error: {name: Use concatMap}` - the hint to use `concatMap` is an error (you may also use `warn` or `suggest` in place of `error` for other severity levels).
 
 These directives are applied in the order they are given, with later hints overriding earlier ones.
@@ -354,19 +383,73 @@
   - {name: unsafePerformIO, within: CompatLayer}
 ```
 
-This declares that the `nub` function can't be used in any modules, and thus is banned from the code. That's probably a good idea, as most people should use an alternative that isn't _O(n^2)_ (e.g. [`nubOrd`](https://hackage.haskell.org/package/extra/docs/Data-List-Extra.html#v:nubOrd)). We also whitelist where `unsafePerformIO` can occur, ensuring that there can be a centrally reviewed location to declare all such instances. Finally, we can restrict the use of modules with:
+This declares that the `nub` function can't be used in any modules, and thus is banned from the code. That's probably a good idea, as most people should use an alternative that isn't _O(n^2)_ (e.g. [`nubOrd`](https://hackage.haskell.org/package/extra/docs/Data-List-Extra.html#v:nubOrd)). We also whitelist where `unsafePerformIO` can occur, ensuring that there can be a centrally reviewed location to declare all such instances. Function names can be given qualified, e.g. `Data.List.head`, but note that functions available through multiple exports (e.g. `head` is also available from `Prelude`) should be listed through all paths they are likely to be obtained, as the HLint qualified matching is unaware of re-exports.
 
+Finally, we can restrict the use of modules with:
+
 ```yaml
 - modules:
   - {name: [Data.Set, Data.HashSet], as: Set}
   - {name: Control.Arrow, within: []}
   - {name: Control.Monad.State, badidents: [modify, get, put], message: "Use Control.Monad.State.Class instead"}
+  - {name: Control.Exception, only: [Exception], message: "Use UnliftIO.Exception instead"}
 ```
 
-This fragment requires that all imports of `Set` must be `qualified Data.Set as Set`, enforcing consistency. It also ensures the module `Control.Arrow` can't be used anywhere. It also prevents explicit imports of the `modify` identifier from `Control.Monad.State` (this is meant to allow you to prevent people from importing reexported identifiers).
+This fragment adds the following hints:
+* Requires that all imports of `Set` must be `qualified Data.Set as Set`, enforcing consistency
+* Ensures the module `Control.Arrow` can't be used anywhere
+* Prevents explicit imports of the given identifiers from `Control.Monad.State` (e.g. to prevent people from importing reexported identifiers).
+* Prevents all imports from `Control.Exception`, except `Exception`
 
 You can customize the `Note:` for restricted modules, functions and extensions, by providing a `message` field (default: `may break the code`).
 
+Other options are available:
+
+- `asRequired`: boolean.
+
+  If true, `as` alias is required. Ignored if `as` is empty.
+- `importStyle`: one of `'qualified'`, `'unqualified'`, `'explicit'`,
+  `'explicitOrQualified'`, `'unrestricted'`.
+
+  The preferred import style.
+
+  `explicitOrQualified` accepts both `import Foo (a,b,c)` and `import qualified Foo`, but not `import Foo` or `import Foo hiding (x)`.
+
+  `explicit` is basically the same, but doesn't accept `import qualified`.
+
+  `qualified` and `unqualified` do not care about the import list at all.
+- `qualifiedStyle`: either `'pre'`, `'post'` or `'unrestricted'`; how should the module be qualified? This option also affects how suggestions are formatted.
+
+For example:
+
+```yaml
+- modules:
+  - {name: [Data.Set, Data.HashSet], as: Set, asRequired: true}
+  - {name: Debug, importStyle: explicitOrQualified}
+  - {name: Unsafe, importStyle: qualified, qualifiedStyle: post, as: Unsafe}
+  - {name: Prelude, importStyle: unqualified}
+```
+
+This:
+* Requires `Data.Set` and `Data.HashSet` to be imported with alias `Set`; if imported without alias, a warning is generated.
+* Says that `Debug` must be imported either qualified with post-qualification, i.e. `import Debug qualified`, or with an explicit import list, e.g. `Debug (debugPrint)`.
+* Requires that `Unsafe` must always be imported qualified, and can't be aliased.
+* Forbids `import qualified Prelude` and `import Prelude qualified` (with or without explicit import list).
+
+You can match on module names using [glob](https://en.wikipedia.org/wiki/Glob_(programming))-style wildcards. Module names are treated like file paths, except that periods in module names are like directory separators in file paths. So `**.*Spec` will match `Spec`, `PreludeSpec`, `Data.ListSpec`, and many more. But `*Spec` won't match `Data.ListSpec` because of the separator. See [the filepattern library](https://hackage.haskell.org/package/filepattern) for a more thorough description of the matching.
+
+Restrictions are unified between wildcard and specific matches. With `asRequired`, `importStyle` and `qualifiedStyle` fields, the more specific option takes precedence. The list fields are merged. With multiple wildcard matches, the precedence between them is not guaranteed (but in practice, names are sorted in the reverse lexicograpic order, and the first one wins -- which hopefully means the more specific one more often than not)
+
+If the same module is specified multiple times, for `asRequired`, `importStyle`
+and `qualifiedStyle` fields, only the first definition will take effect.
+
+```yaml
+- modules:
+  - {name: [Data.Map, Data.Map.*], as: Map}
+  - {name: Test.Hspec, within: **.*Spec }
+  - {name: '**', qualifiedStyle: post}
+```
+
 ## Hacking HLint
 
 Contributions to HLint are most welcome, following [my standard contribution guidelines](https://github.com/ndmitchell/neil/blob/master/README.md#contributions).
@@ -398,6 +481,10 @@
 Getting started on problems in HLint often means wanting to inspect a GHC parse tree to get a sense of what it looks like (to see how to match on it for example). Given a source program `Foo.hs` (say), you can get GHC to print a textual representation of `Foo`'s AST via the `-ddump-parsed-ast` flag e.g. `ghc -fforce-recomp -ddump-parsed-ast -c Foo.hs`.
 
 When you have an [`HsSyn`](https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/compiler/hs-syn-type) term in your program, it's quite common to want to print it (e.g. via `Debug.Trace.trace`). Types in `HsSyn` aren't in [`Show`](https://hoogle.haskell.org/?hoogle=Show). Not all types in `HsSyn` are [`Outputable`](https://hoogle.haskell.org/?hoogle=Outputable) but when they are you can call `ppr` to get `SDoc`s. This idiom is common enough that there exists [`unsafePrettyPrint`](https://hackage.haskell.org/package/ghc-lib-parser-ex-8.10.0.16/docs/Language-Haskell-GhclibParserEx-GHC-Utils-Outputable.html#v:unsafePrettyPrint). The function [`showAstData`](https://hoogle.haskell.org/?hoogle=showAstData) can be called on any `HsSyn` term to get output like with the `dump-parsed-ast` flag. The `showAstData` approach is preferable to `ppr` when both choices exist in that two ASTs that differ only in fixity arrangements will render differently with the former.
+
+### Generating the hints summary
+
+The hints summary is an auto-generated list of hlint's builtin hints. This can be generated with `hlint --generate-summary`, which will output the summary to `hints.md`.
 
 ### Acknowledgements
 
diff --git a/data/default.yaml b/data/default.yaml
--- a/data/default.yaml
+++ b/data/default.yaml
@@ -51,6 +51,9 @@
 #
 # Generalise map to fmap, ++ to <>
 # - group: {name: generalise, enabled: true}
+#
+# Warn on use of partial functions
+# - group: {name: partial, enabled: true}
 
 
 # Ignore some builtin hints
diff --git a/data/do-block.yaml b/data/do-block.yaml
new file mode 100644
--- /dev/null
+++ b/data/do-block.yaml
@@ -0,0 +1,2 @@
+- ignore: { }
+- warn: {lhs: "case m of Just x -> y; Nothing -> Nothing", rhs: "do x <- m; y"}
diff --git a/data/hintrule-implies-classify.yaml b/data/hintrule-implies-classify.yaml
new file mode 100644
--- /dev/null
+++ b/data/hintrule-implies-classify.yaml
@@ -0,0 +1,2 @@
+- ignore: { }
+- warn: {lhs: mapM, rhs: traverse}
diff --git a/data/hlint.yaml b/data/hlint.yaml
--- a/data/hlint.yaml
+++ b/data/hlint.yaml
@@ -6,36 +6,41 @@
 - package:
     name: base
     modules:
-    - import Prelude
+    - import Control.Applicative
     - import Control.Arrow
+    - import Control.Concurrent.Chan
     - import Control.Exception
+    - import Control.Exception.Base
     - import Control.Monad
     - import Control.Monad.Trans.State
-    - import qualified Data.Foldable
-    - import Data.Foldable(asum, sequenceA_, traverse_, for_)
-    - import Data.Traversable(traverse, for)
-    - import Control.Applicative
     - import Data.Bifunctor
+    - import Data.Char
+    - import Data.Either
+    - import Data.Fixed
+    - import Data.Foldable(asum, sequenceA_, traverse_, for_)
     - import Data.Function
     - import Data.Int
-    - import Data.Char
     - import Data.List as Data.List
     - import Data.List as X
+    - import Data.List.NonEmpty
     - import Data.Maybe
     - import Data.Monoid
+    - import Data.Ratio
+    - import Data.Traversable(traverse, for)
+    - import Numeric
+    - import Prelude
+    - import System.Exit
     - import System.IO
-    - import Control.Concurrent.Chan
     - import System.Mem.Weak
-    - import Control.Exception.Base
-    - import System.Exit
-    - import Data.Either
-    - import Numeric
+    - import Text.Read
+    - import qualified Data.Foldable
 
     - import IO as System.IO
     - import List as Data.List
     - import Maybe as Data.Maybe
     - import Monad as Control.Monad
     - import Char as Data.Char
+    - import Language.Haskell.TH as TH
 
 - package:
     name: lens
@@ -51,6 +56,11 @@
     - import Data.Attoparsec.ByteString
 
 - package:
+    name: quickcheck
+    modules:
+    - import Test.QuickCheck
+
+- package:
     name: codeworld-api
     modules:
     - import CodeWorld
@@ -65,6 +75,8 @@
     # I/O
 
     - warn: {lhs: putStrLn (show x), rhs: print x}
+    - warn: {lhs: putStr (x ++ "\n"), rhs: putStrLn x}
+    - warn: {lhs: putStr (x ++ y ++ "\n"), rhs: putStrLn (x ++ y)}
     - warn: {lhs: mapM_ putChar, rhs: putStr}
     - warn: {lhs: hGetChar stdin, rhs: getChar}
     - warn: {lhs: hGetLine stdin, rhs: getLine}
@@ -104,18 +116,27 @@
     - warn: {lhs: head (sortBy f x), rhs: minimumBy f x, side: isCompare f}
     - warn: {lhs: last (sortBy f x), rhs: maximumBy f x, side: isCompare f}
     - warn: {lhs: reverse (sortBy f x), rhs: sortBy (flip f) x, name: Avoid reverse, side: isCompare f, note: Stabilizes sort order}
-    - warn: {lhs: sortBy (flip (comparing f)), rhs: sortOn (Down . f)}
-    - warn: {lhs: sortBy (comparing f), rhs: sortOn f, side: notEq f fst && notEq f snd}
+    - warn: {lhs: sortBy (flip (comparing f)), rhs: sortBy (comparing (Data.Ord.Down . f))}
     - warn: {lhs: reverse (sortOn f x), rhs: sortOn (Data.Ord.Down . f) x, name: Avoid reverse, note: Stabilizes sort order}
-    # This suggestion likely costs performance, see https://github.com/ndmitchell/hlint/issues/669#issuecomment-607154496
-    # - warn: {lhs: reverse (sort x), rhs: sortOn Data.Ord.Down x, name: Avoid reverse, note: Stabilizes sort order}
+    - warn: {lhs: reverse (sort x), rhs: sortBy (comparing Data.Ord.Down) x, name: Avoid reverse, note: Stabilizes sort order}
     - hint: {lhs: flip (g `on` h), rhs: flip g `on` h, name: Move flip}
-    - hint: {lhs: (f `on` g) `on` h, rhs: f `on` (g . h), name: Fuse on/on}
+    - hint: {lhs: (f `on` g) `on` h, rhs: f `on` (g . h), name: Use on once}
+    - warn: {lhs: if a >= b then a else b, rhs: max a b}
+    - warn: {lhs: if a >= b then b else a, rhs: min a b}
+    - warn: {lhs: if a > b then a else b, rhs: max a b}
+    - warn: {lhs: if a > b then b else a, rhs: min a b}
+    - warn: {lhs: if a <= b then a else b, rhs: min a b}
+    - warn: {lhs: if a <= b then b else a, rhs: max a b}
+    - warn: {lhs: if a < b then a else b, rhs: min a b}
+    - warn: {lhs: if a < b then b else a, rhs: max a b}
+    - warn: {lhs: "maximum [a, b]", rhs: max a b}
+    - warn: {lhs: "minimum [a, b]", rhs: min a b}
 
 
     # READ/SHOW
 
     - warn: {lhs: showsPrec 0 x "", rhs: show x}
+    - warn: {lhs: "showsPrec 0 x []", rhs: show x}
     - warn: {lhs: readsPrec 0, rhs: reads}
     - warn: {lhs: showsPrec 0, rhs: shows}
     - hint: {lhs: showIntAtBase 16 intToDigit, rhs: showHex}
@@ -124,20 +145,27 @@
     # LIST
 
     - warn: {lhs: concat (map f x), rhs: concatMap f x}
+    - warn: {lhs: concat (f <$> x), rhs: concatMap f x}
+    - warn: {lhs: concat (x <&> f), rhs: concatMap f x}
     - warn: {lhs: concat (fmap f x), rhs: concatMap f x}
     - hint: {lhs: "concat [a, b]", rhs: a ++ b}
+    - hint: {lhs: "sum [a, b]", rhs: a + b, name: "Use + directly"}
     - hint: {lhs: map f (map g x), rhs: map (f . g) x, name: Use map once}
     - hint: {lhs: concatMap f (map g x), rhs: concatMap (f . g) x, name: Fuse concatMap/map}
     - hint: {lhs: x !! 0, rhs: head x}
     - warn: {lhs: take n (repeat x), rhs: replicate n x}
     - warn: {lhs: map f (replicate n x), rhs: replicate n (f x)}
     - warn: {lhs: map f (repeat x), rhs: repeat (f x)}
+    - warn: {lhs: concatMap f (repeat x), rhs: cycle (f x)}
+    - warn: {lhs: concat (repeat x), rhs: cycle x}
     - warn: {lhs: "cycle [x]", rhs: repeat x}
     - warn: {lhs: head (reverse x), rhs: last x}
+    - warn: {lhs: last (reverse x), rhs: head x, note: IncreasesLaziness}
     - warn: {lhs: head (drop n x), rhs: x !! n, side: isNat n}
     - warn: {lhs: head (drop n x), rhs: x !! max 0 n, side: not (isNat n) && not (isNeg n)}
+    - warn: {lhs: reverse (init x), rhs: tail (reverse x)}
     - warn: {lhs: reverse (tail (reverse x)), rhs: init x, note: IncreasesLaziness}
-    - warn: {lhs: reverse (reverse x), rhs: x, note: IncreasesLaziness, name: Avoid reverse}
+    - warn: {lhs: reverse (reverse x), rhs: x, note: IncreasesLaziness, name: Redundant reverse}
     - warn: {lhs: isPrefixOf (reverse x) (reverse y), rhs: isSuffixOf x y}
     - warn: {lhs: "foldr (++) []", rhs: concat}
     - warn: {lhs: foldr (++) "", rhs: concat}
@@ -151,6 +179,8 @@
     - warn: {lhs: foldr (.) id l z, rhs: foldr ($) z l}
     - warn: {lhs: span (not . p), rhs: break p}
     - warn: {lhs: break (not . p), rhs: span p}
+    - warn: {lhs: span (notElem x), rhs: break (elem x), name: Use break}
+    - warn: {lhs: break (notElem x), rhs: span (elem x), name: Use span}
     - warn: {lhs: "(takeWhile p x, dropWhile p x)", rhs: span p x, note: DecreasesLaziness}
     - warn: {lhs: fst (span p x), rhs: takeWhile p x}
     - warn: {lhs: snd (span p x), rhs: dropWhile p x}
@@ -184,12 +214,16 @@
     - hint: {lhs: "\\x -> [x]", rhs: "(:[])", name: "Use :"}
     - hint: {lhs: map f (zip x y), rhs: zipWith (curry f) x y, side: not (isApp f)}
     - hint: {lhs: "map f (fromMaybe [] x)", rhs: "maybe [] (map f) x"}
+    - hint: {lhs: "concatMap f (fromMaybe [] x)", rhs: "maybe [] (concatMap f) x"}
+    - hint: {lhs: "concat (fromMaybe [] x)", rhs: "maybe [] concat x"}
     - warn: {lhs: not (elem x y), rhs: notElem x y}
+    - warn: {lhs: not (notElem x y), rhs: elem x y}
     - hint: {lhs: foldr f z (map g x), rhs: foldr (f . g) z x, name: Fuse foldr/map}
     - warn: {lhs: "x ++ concatMap (' ':) y", rhs: "unwords (x:y)"}
     - warn: {lhs: intercalate " ", rhs: unwords}
     - hint: {lhs: concat (intersperse x y), rhs: intercalate x y, side: notEq x " "}
     - hint: {lhs: concat (intersperse " " x), rhs: unwords x}
+    - warn: {lhs: null (concat x), rhs: all null x}
     - warn: {lhs: null (filter f x), rhs: not (any f x), name: Use any}
     - warn: {lhs: "filter f x == []", rhs: not (any f x), name: Use any}
     - warn: {lhs: "filter f x /= []", rhs: any f x}
@@ -207,6 +241,10 @@
     - warn: {lhs: notElem False, rhs: and}
     - warn: {lhs: True `elem` l, rhs: or l}
     - warn: {lhs: False `notElem` l, rhs: and l}
+    - warn: {lhs: elem False, rhs: any not, name: Use any}
+    - warn: {lhs: notElem True, rhs: all not, name: Use all}
+    - warn: {lhs: False `elem` l, rhs: any not l, name: Use any}
+    - warn: {lhs: True `notElem` l, rhs: all not l, name: Use all}
     - warn: {lhs: findIndex ((==) a), rhs: elemIndex a}
     - warn: {lhs: findIndex (a ==), rhs: elemIndex a}
     - warn: {lhs: findIndex (== a), rhs: elemIndex a}
@@ -216,6 +254,7 @@
     - warn: {lhs: "lookup b (zip l [0..])", rhs: elemIndex b l}
     - hint: {lhs: "elem x [y]", rhs: x == y, note: ValidInstance Eq a}
     - hint: {lhs: "notElem x [y]", rhs: x /= y, note: ValidInstance Eq a}
+    - hint: {lhs: "length [1..n]", rhs: max 0 n}
     - hint: {lhs: length x >= 0, rhs: "True", name: Length always non-negative}
     - hint: {lhs: 0 <= length x, rhs: "True", name: Length always non-negative}
     - hint: {lhs: length x > 0, rhs: not (null x), note: IncreasesLaziness, name: Use null}
@@ -226,9 +265,13 @@
     - warn: {lhs: drop i x, rhs: x, side: isNegZero i, name: Drop on a non-positive}
     - warn: {lhs: last (scanl f z x), rhs: foldl f z x}
     - warn: {lhs: head (scanr f z x), rhs: foldr f z x}
+    - warn: {lhs: "scanl (\\x _ -> a) b (replicate c d)", rhs: "take c (iterate (\\x -> a) b)"}
+    - warn: {lhs: "foldl (\\x _ -> a) b [1..c]", rhs: "iterate (\\x -> a) b !! c"}
     - warn: {lhs: iterate id, rhs: repeat}
     - warn: {lhs: zipWith f (repeat x), rhs: map (f x)}
+    - warn: {lhs: zip (repeat x), rhs: "map (_noParen_ x,)", note: RequiresExtension TupleSections, name: "Use map with tuple-section"}
     - warn: {lhs: zipWith f y (repeat z), rhs: map (`f` z) y}
+    - warn: {lhs: zip y (repeat z), rhs: "map (,_noParen_ z) y", note: RequiresExtension TupleSections, name: "Use map with tuple-section"}
     - warn: {lhs: listToMaybe (filter p x), rhs: find p x}
     - warn: {lhs: zip (take n x) (take n y), rhs: take n (zip x y)}
     - warn: {lhs: zip (take n x) (take m y), rhs: take (min n m) (zip x y), side: notEq n m, note: [IncreasesLaziness, DecreasesLaziness], name: Redundant take}
@@ -239,22 +282,38 @@
     - warn: {lhs: mempty `mappend` x, rhs: x, name: "Monoid law, left identity"}
     - warn: {lhs: x <> mempty, rhs: x, name: "Monoid law, right identity"}
     - warn: {lhs: x `mappend` mempty, rhs: x, name: "Monoid law, right identity"}
-    - warn: {lhs: foldr (<>) mempty, rhs: mconcat}
-    - warn: {lhs: foldr mappend mempty, rhs: mconcat}
+    - warn: {lhs: foldr (<>) mempty, rhs: Data.Foldable.fold}
+    - warn: {lhs: foldr mappend mempty, rhs: Data.Foldable.fold}
     - warn: {lhs: mempty x, rhs: mempty, name: Evaluate}
     - warn: {lhs: x `mempty` y, rhs: mempty, name: Evaluate, note: "Make sure you didn't mean to use mappend instead of mempty"}
 
     # TRAVERSABLES
 
+    - warn: {lhs: traverse pure, rhs: pure, name: "Traversable law"}
+    - warn: {lhs: traverse (pure . f) x, rhs: pure (fmap f x), name: "Traversable law"}
     - warn: {lhs: sequenceA (map f x), rhs: traverse f x}
+    - warn: {lhs: sequenceA (f <$> x), rhs: traverse f x}
+    - warn: {lhs: sequenceA (x <&> f), rhs: traverse f x}
     - warn: {lhs: sequenceA (fmap f x), rhs: traverse f x}
     - warn: {lhs: sequenceA_ (map f x), rhs: traverse_ f x}
+    - warn: {lhs: sequenceA_ (f <$> x), rhs: traverse_ f x}
+    - warn: {lhs: sequenceA_ (x <&> f), rhs: traverse_ f x}
     - warn: {lhs: sequenceA_ (fmap f x), rhs: traverse_ f x}
     - warn: {lhs: foldMap id, rhs: fold}
+    - warn: {lhs: fold (f <$> x), rhs: foldMap f x}
+    - warn: {lhs: fold (x <&> f), rhs: foldMap f x}
     - warn: {lhs: fold (fmap f x), rhs: foldMap f x}
     - warn: {lhs: fold (map f x), rhs: foldMap f x}
+    - warn: {lhs: foldMap f (g <$> x), rhs: foldMap (f . g) x, name: Fuse foldMap/<$>}
+    - warn: {lhs: foldMap f (x <&> g), rhs: foldMap (f . g) x, name: Fuse foldMap/<&>}
     - warn: {lhs: foldMap f (fmap g x), rhs: foldMap (f . g) x, name: Fuse foldMap/fmap}
     - warn: {lhs: foldMap f (map g x), rhs: foldMap (f . g) x, name: Fuse foldMap/map}
+    - warn: {lhs: traverse f (fmap g x), rhs: traverse (f . g) x, name: Fuse traverse/fmap}
+    - warn: {lhs: traverse f (g <$> x), rhs: traverse (f . g) x, name: Fuse traverse/<$>}
+    - warn: {lhs: traverse f (x <&> g), rhs: traverse (f . g) x, name: Fuse traverse/<&>}
+    - warn: {lhs: traverse_ f (fmap g x), rhs: traverse_ (f . g) x, name: Fuse traverse_/fmap}
+    - warn: {lhs: traverse_ f (g <$> x), rhs: traverse_ (f . g) x, name: Fuse traverse_/<$>}
+    - warn: {lhs: traverse_ f (x <&> g), rhs: traverse_ (f . g) x, name: Fuse traverse_/<&>}
 
     # BY
 
@@ -270,6 +329,7 @@
 
     # FOLDS
 
+    - warn: {lhs: foldr  (>>) (pure ()), rhs: sequence_}
     - warn: {lhs: foldr  (>>) (return ()), rhs: sequence_}
     - warn: {lhs: foldr  (&&) True, rhs: and}
     - warn: {lhs: foldl  (&&) True, rhs: and, note: IncreasesLaziness}
@@ -307,11 +367,12 @@
     - warn: {lhs: f (fst p) (snd p), rhs: uncurry f p}
     - warn: {lhs: "uncurry (\\x y -> z)", rhs: "\\(x,y) -> z"}
     - warn: {lhs: "curry (\\(x,y) -> z)", rhs: "\\x y -> z"}
-    - warn: {lhs: uncurry (curry f), rhs: f}
-    - warn: {lhs: curry (uncurry f), rhs: f}
+    - warn: {lhs: uncurry (curry f), rhs: f, name: Redundant curry/uncurry}
+    - warn: {lhs: curry (uncurry f), rhs: f, name: Redundant curry/uncurry}
+    - warn: {lhs: "uncurry f (a, b)", rhs: f a b}
     - warn: {lhs: ($) (f x), rhs: f x, name: Redundant $}
     - warn: {lhs: (f $), rhs: f, name: Redundant $}
-    - warn: {lhs: (Data.Function.& f), rhs: f, name: Redundant Data.Function.&}
+    - warn: {lhs: (& f), rhs: f, name: Redundant &}
     - hint: {lhs: \x -> y, rhs: const y, side: isAtom y && not (isWildcard y)}
         # If any isWildcard recursively then x may be used but not mentioned explicitly
     - warn: {lhs: flip f x y, rhs: f y x, side: isApp original && isAtom y}
@@ -325,13 +386,18 @@
     - warn: {lhs: g <**> flip f, rhs: g >>= f, name: Redundant flip}
     - warn: {lhs: flip f =<< g, rhs: f <*> g, name: Redundant flip}
     - warn: {lhs: g >>= flip f, rhs: g Control.Applicative.<**> f, name: Redundant flip}
+    - warn: {lhs: ($ x) . f, rhs: flip f x}
 
     # CHAR
 
-    - warn: {lhs: a >= 'a' && a <= 'z', rhs: isAsciiLower a}
-    - warn: {lhs: a >= 'A' && a <= 'Z', rhs: isAsciiUpper a}
-    - warn: {lhs: a >= '0' && a <= '9', rhs: isDigit a}
-    - warn: {lhs: a >= '0' && a <= '7', rhs: isOctDigit a}
+    - warn: {lhs: "a >= 'a' && a <= 'z'", rhs: isAsciiLower a}
+    - warn: {lhs: "'a' <= a && a <= 'z'", rhs: isAsciiLower a}
+    - warn: {lhs: "a >= 'A' && a <= 'Z'", rhs: isAsciiUpper a}
+    - warn: {lhs: "'A' <= a && a <= 'Z'", rhs: isAsciiUpper a}
+    - warn: {lhs: "a >= '0' && a <= '9'", rhs: isDigit a}
+    - warn: {lhs: "'0' <= a && a <= '9'", rhs: isDigit a}
+    - warn: {lhs: "a >= '0' && a <= '7'", rhs: isOctDigit a}
+    - warn: {lhs: "'0' <= a && a <= '7'", rhs: isOctDigit a}
     - warn: {lhs: isLower a || isUpper a, rhs: isAlpha a}
     - warn: {lhs: isUpper a || isLower a, rhs: isAlpha a}
 
@@ -341,10 +407,18 @@
     - hint: {lhs: x == False, rhs: not x, name: Redundant ==}
     - warn: {lhs: True == a, rhs: a, name: Redundant ==}
     - hint: {lhs: False == a, rhs: not a, name: Redundant ==}
+    - hint: {lhs: (== True), rhs: id, name: Redundant ==}
+    - hint: {lhs: (== False), rhs: not, name: Redundant ==}
+    - hint: {lhs: (True ==), rhs: id, name: Redundant ==}
+    - hint: {lhs: (False ==), rhs: not, name: Redundant ==}
     - warn: {lhs: a /= True, rhs: not a, name: Redundant /=}
     - hint: {lhs: a /= False, rhs: a, name: Redundant /=}
     - warn: {lhs: True /= a, rhs: not a, name: Redundant /=}
     - hint: {lhs: False /= a, rhs: a, name: Redundant /=}
+    - hint: {lhs: (/= True), rhs: not, name: Redundant /=}
+    - hint: {lhs: (/= False), rhs: id, name: Redundant /=}
+    - hint: {lhs: (True /=), rhs: not, name: Redundant /=}
+    - hint: {lhs: (False /=), rhs: id, name: Redundant /=}
     - warn: {lhs: if a then x else x, rhs: x, note: IncreasesLaziness, name: Redundant if}
     - warn: {lhs: if a then True else False, rhs: a, name: Redundant if}
     - warn: {lhs: if a then False else True, rhs: not a, name: Redundant if}
@@ -401,11 +475,15 @@
     # FUNCTOR
 
     - warn: {lhs: fmap f (fmap g x), rhs: fmap (f . g) x, name: Functor law}
-    - warn: {lhs: f <$> g <$> x, rhs: f . g <$> x, name: Functor law}
+    - warn: {lhs: f <$> (g <$> x), rhs: f . g <$> x, name: Functor law}
+    - warn: {lhs: x <&> g <&> f, rhs: x <&> f . g, name: Functor law}
     - warn: {lhs: fmap id, rhs: id, name: Functor law}
     - warn: {lhs: id <$> x, rhs: x, name: Functor law}
+    - warn: {lhs: x <&> id, rhs: x, name: Functor law}
+    - warn: {lhs: f <$> g <$> x, rhs: f . g <$> x}
     - hint: {lhs: fmap f $ x, rhs: f <$> x, side: isApp x || isAtom x}
     - hint: {lhs: \x -> a <$> b x, rhs: fmap a . b}
+    - hint: {lhs: \x -> b x <&> a, rhs: fmap a . b}
     - hint: {lhs: x *> pure y, rhs: x Data.Functor.$> y}
     - hint: {lhs: x *> return y, rhs: x Data.Functor.$> y}
     - hint: {lhs: pure x <* y, rhs: x Data.Functor.<$ y}
@@ -416,23 +494,43 @@
     - hint: {lhs: x <&> const y, rhs: x Data.Functor.$> y}
     - hint: {lhs: x <&> pure y, rhs: x Data.Functor.$> y}
     - hint: {lhs: x <&> return y, rhs: x Data.Functor.$> y}
+    - warn: {lhs: "fmap f (x,b)", rhs: "(x,f b)", name: Using fmap on tuple}
+    - warn: {lhs: "f <$> (x,b)", rhs: "(x,f b)", name: Using <$> on tuple}
+    - warn: {lhs: "(x,b) <&> f", rhs: "(x,f b)", name: Using <&> on tuple}
+    - warn: {lhs: "fmap f (x,y,b)", rhs: "(x,y,f b)", name: Using fmap on tuple}
+    - warn: {lhs: "f <$> (x,y,b)", rhs: "(x,y,f b)", name: Using <$> on tuple}
+    - warn: {lhs: "(x,y,b) <&> f", rhs: "(x,y,f b)", name: Using <&> on tuple}
 
     # APPLICATIVE
 
-    - hint: {lhs: return x <*> y, rhs: x <$> y}
     - hint: {lhs: pure x <*> y, rhs: x <$> y}
+    - hint: {lhs: return x <*> y, rhs: x <$> y}
     - warn: {lhs: x <* pure y, rhs: x}
-    - warn: {lhs: pure x *> y, rhs: "y"}
+    - warn: {lhs: x <* return y, rhs: x}
+    - warn: {lhs: pure x *> y, rhs: "y", name: Redundant *>}
+    - warn: {lhs: return x *> y, rhs: "y", name: Redundant *>}
+    - warn: {lhs: "x <*> Nothing", rhs: "Nothing" }
+    - warn: {lhs: "x <*> First Nothing", rhs: "First Nothing" }
+    - warn: {lhs: "x <*> Last Nothing", rhs: "Last Nothing" }
+    - warn: {lhs: "x <*> Left a", rhs: "Left a" }
+    - warn: {lhs: "x <*> Ap []", rhs: "Ap []" }
+    - warn: {lhs: "x <*> []", rhs: "[]" }
 
     # MONAD
 
+    - warn: {lhs: pure a >>= f, rhs: f a, name: "Monad law, left identity"}
     - warn: {lhs: return a >>= f, rhs: f a, name: "Monad law, left identity"}
+    - warn: {lhs: f =<< pure a, rhs: f a, name: "Monad law, left identity"}
     - warn: {lhs: f =<< return a, rhs: f a, name: "Monad law, left identity"}
+    - warn: {lhs: m >>= pure, rhs: m, name: "Monad law, right identity"}
     - warn: {lhs: m >>= return, rhs: m, name: "Monad law, right identity"}
+    - warn: {lhs: pure =<< m, rhs: m, name: "Monad law, right identity"}
     - warn: {lhs: return =<< m, rhs: m, name: "Monad law, right identity"}
     - warn: {lhs: liftM, rhs: fmap}
     - warn: {lhs: liftA, rhs: fmap}
+    - hint: {lhs: m >>= pure . f, rhs: m Data.Functor.<&> f}
     - hint: {lhs: m >>= return . f, rhs: m Data.Functor.<&> f}
+    - hint: {lhs: pure . f =<< m, rhs: f <$> m}
     - hint: {lhs: return . f =<< m, rhs: f <$> m}
     - warn: {lhs: fmap f x >>= g, rhs: x >>= g . f}
     - warn: {lhs: f <$> x >>= g, rhs: x >>= g . f}
@@ -440,29 +538,41 @@
     - warn: {lhs: g =<< fmap f x, rhs: g . f =<< x}
     - warn: {lhs: g =<< f <$> x, rhs: g . f =<< x}
     - warn: {lhs: g =<< (x Data.Functor.<&> f), rhs: g . f =<< x}
+    - warn: {lhs: if x then y else pure (), rhs: Control.Monad.when x $ _noParen_ y, side: not (isAtom y)}
     - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x $ _noParen_ y, side: not (isAtom y)}
+    - warn: {lhs: if x then y else pure (), rhs: Control.Monad.when x y, side: isAtom y}
     - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x y, side: isAtom y}
+    - warn: {lhs: if x then pure () else y, rhs: Control.Monad.unless x $ _noParen_ y, side: isAtom y}
     - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x $ _noParen_ y, side: isAtom y}
+    - warn: {lhs: if x then pure () else y, rhs: Control.Monad.unless x y, side: isAtom y}
     - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x y, side: isAtom y}
     - warn: {lhs: sequence (map f x), rhs: mapM f x}
     - warn: {lhs: sequence_ (map f x), rhs: mapM_ f x}
+    - warn: {lhs: sequence (f <$> x), rhs: mapM f x}
+    - warn: {lhs: sequence (x <&> f), rhs: mapM f x}
     - warn: {lhs: sequence (fmap f x), rhs: mapM f x}
+    - warn: {lhs: sequence_ (f <$> x), rhs: mapM_ f x}
+    - warn: {lhs: sequence_ (x <&> f), rhs: mapM_ f x}
     - warn: {lhs: sequence_ (fmap f x), rhs: mapM_ f x}
     - hint: {lhs: flip mapM, rhs: Control.Monad.forM}
     - hint: {lhs: flip mapM_, rhs: Control.Monad.forM_}
     - hint: {lhs: flip forM, rhs: mapM}
     - hint: {lhs: flip forM_, rhs: mapM_}
     - warn: {lhs: when (not x), rhs: unless x}
+    - warn: {lhs: when (notElem x y), rhs: unless (elem x y), name: Use unless}
     - warn: {lhs: unless (not x), rhs: when x}
+    - warn: {lhs: unless (notElem x y), rhs: when (elem x y), name: Use when}
     - warn: {lhs: x >>= id, rhs: Control.Monad.join x}
     - warn: {lhs: id =<< x, rhs: Control.Monad.join x}
-    - warn: {lhs: id =<< x, rhs: Control.Monad.join x}
-    - warn: {lhs: id =<< x, rhs: Control.Monad.join x}
     - hint: {lhs: join (f <$> x), rhs: f =<< x}
+    - hint: {lhs: join (x <&> f), rhs: f =<< x}
     - hint: {lhs: join (fmap f x), rhs: f =<< x}
+    - hint: {lhs: a >> pure (), rhs: Control.Monad.void a, side: isAtom a || isApp a}
     - hint: {lhs: a >> return (), rhs: Control.Monad.void a, side: isAtom a || isApp a}
     - warn: {lhs: fmap (const ()), rhs: Control.Monad.void}
     - warn: {lhs: const () <$> x, rhs: Control.Monad.void x}
+    - warn: {lhs: x <&> const (), rhs: Control.Monad.void x}
+    - warn: {lhs: () <$ x, rhs: Control.Monad.void x}
     - warn: {lhs: flip (>=>), rhs: (<=<)}
     - warn: {lhs: flip (<=<), rhs: (>=>)}
     - warn: {lhs: flip (>>=), rhs: (=<<)}
@@ -473,19 +583,30 @@
     - hint: {lhs: (f =<<) . g, rhs: f Control.Monad.<=< g}
     - warn: {lhs: a >> forever a, rhs: forever a}
     - hint: {lhs: liftM2 id, rhs: ap}
+    - warn: {lhs: liftM2 f (pure x), rhs: fmap (f x)}
     - warn: {lhs: liftA2 f (return x), rhs: fmap (f x)}
     - warn: {lhs: liftM2 f (pure x), rhs: fmap (f x)}
     - warn: {lhs: liftM2 f (return x), rhs: fmap (f x)}
+    - warn: {lhs: fmap f (pure x), rhs: pure (f x)}
     - warn: {lhs: fmap f (return x), rhs: return (f x)}
+    - warn: {lhs: f <$> pure x, rhs: pure (f x)}
+    - warn: {lhs: pure x <&> f, rhs: pure (f x)}
     - warn: {lhs: f <$> return x, rhs: return (f x)}
+    - warn: {lhs: return x <&> f, rhs: return (f x)}
     - warn: {lhs: mapM (uncurry f) (zip l m), rhs: zipWithM f l m}
     - warn: {lhs: mapM_ (void . f), rhs: mapM_ f}
     - warn: {lhs: forM_ x (void . f), rhs: forM_ x f}
     - warn: {lhs: a >>= \_ -> b, rhs: a >> b}
+    - warn: {lhs: m <* pure x, rhs: m}
     - warn: {lhs: m <* return x, rhs: m}
-    - warn: {lhs: return x *> m, rhs: m}
-    - warn: {lhs: pure x >> m, rhs: m}
-    - warn: {lhs: return x >> m, rhs: m}
+    - warn: {lhs: pure x *> m, rhs: m, name: Redundant *>}
+    - warn: {lhs: return x *> m, rhs: m, name: Redundant *>}
+    - warn: {lhs: pure x >> m, rhs: m, name: Redundant >>}
+    - warn: {lhs: return x >> m, rhs: m, name: Redundant >>}
+    - warn: {lhs: "forM [1..n] (const f)", rhs: replicateM n f}
+    - warn: {lhs: "for [1..n] (const f)", rhs: replicateM n f}
+    - warn: {lhs: "forM [1..n] (\\_ -> x)", rhs: replicateM n x}
+    - warn: {lhs: "for [1..n] (\\_ -> x)", rhs: replicateM n x}
 
     # STATE MONAD
 
@@ -494,6 +615,8 @@
 
     # MONAD LIST
 
+    - warn: {lhs: unzip <$> mapM f x, rhs: Control.Monad.mapAndUnzipM f x}
+    - warn: {lhs: mapM f x <&> unzip, rhs: Control.Monad.mapAndUnzipM f x}
     - warn: {lhs: fmap unzip (mapM f x), rhs: Control.Monad.mapAndUnzipM f x}
     - warn: {lhs: sequence (zipWith f x y), rhs: Control.Monad.zipWithM f x y}
     - warn: {lhs: sequence_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y}
@@ -519,14 +642,13 @@
     - warn: {lhs: flip traverse_, rhs: for_}
     - warn: {lhs: flip for_, rhs: traverse_}
     - warn: {lhs: foldr (*>) (pure ()), rhs: sequenceA_}
+    - warn: {lhs: foldr (*>) (return ()), rhs: sequence_}
     - warn: {lhs: foldr (<|>) empty, rhs: asum}
     - warn: {lhs: liftA2 (flip ($)), rhs: (<**>)}
     - warn: {lhs: liftA2 f (pure x), rhs: fmap (f x)}
-    - warn: {lhs: fmap f (pure x), rhs: pure (f x)}
-    - warn: {lhs: f <$> pure x, rhs: pure (f x)}
+    - warn: {lhs: liftA2 f (return x), rhs: fmap (f x)}
     - warn: {lhs: Just <$> a <|> pure Nothing, rhs: optional a}
-    - hint: {lhs: m >>= pure . f, rhs: m Data.Functor.<&> f}
-    - hint: {lhs: pure . f =<< m, rhs: f <$> m}
+    - warn: {lhs: Just <$> a <|> return Nothing, rhs: optional a}
     - warn: {lhs: empty <|> x, rhs: x, name: "Alternative law, left identity"}
     - warn: {lhs: x <|> empty, rhs: x, name: "Alternative law, right identity"}
     - warn: {lhs: traverse id, rhs: sequenceA}
@@ -538,6 +660,7 @@
     - hint: {lhs: "if b then [x] else []", rhs: "[x | b]", name: Use list comprehension}
     - hint: {lhs: "if b then [] else [x]", rhs: "[x | not b]", name: Use list comprehension}
     - hint: {lhs: "[x | x <- y]", rhs: "y", side: isVar x, name: Redundant list comprehension}
+    - hint: {lhs: "[ f x | x <- [y] ]", rhs: "[f y]", side: isVar x, name: Redundant list comprehension}
 
     # SEQ
 
@@ -555,6 +678,9 @@
     - warn: {lhs: snd (unzip x), rhs: map snd x}
     - hint: {lhs: "\\x y -> (x, y)", rhs: "(,)"}
     - hint: {lhs: "\\x y z -> (x, y, z)", rhs: "(,,)"}
+    - hint: {lhs: "(,b) a", rhs: "(a,b)", side: isAtom a, name: Evaluate}
+    - hint: {lhs: "(a,) b", rhs: "(a,b)", side: isAtom b, name: Evaluate}
+    - warn: {lhs: "Data.List.NonEmpty.unzip", rhs: "Data.Functor.unzip", name: "Avoid NonEmpty.unzip", note: "The function is being deprecated"}
 
     # MAYBE
 
@@ -562,18 +688,20 @@
     - warn: {lhs: maybe Nothing Just, rhs: id, name: Redundant maybe}
     - warn: {lhs: maybe False (const True), rhs: Data.Maybe.isJust}
     - warn: {lhs: maybe True (const False), rhs: Data.Maybe.isNothing}
-    - warn: {lhs: maybe False (x ==), rhs: (Just x ==)}
-    - warn: {lhs: maybe True (x /=), rhs: (Just x /=)}
-    - warn: {lhs: maybe False (== x), rhs: (Just x ==), note: ValidInstance Eq x}
-    - warn: {lhs: maybe True (/= x), rhs: (Just x /=), note: ValidInstance Eq x}
+    - warn: {lhs: maybe False (x ==), rhs: (Just x ==), name: Redundant maybe}
+    - warn: {lhs: maybe True (x /=), rhs: (Just x /=), name: Redundant maybe}
+    - warn: {lhs: maybe False (== x), rhs: (Just x ==), note: ValidInstance Eq x, name: Redundant maybe}
+    - warn: {lhs: maybe True (/= x), rhs: (Just x /=), note: ValidInstance Eq x, name: Redundant maybe}
     # The following two hints seem to be somewhat unwelcome, e.g.
     # https://github.com/ndmitchell/hlint/issues/1177
-    - ignore: {lhs: fromMaybe False x, rhs: Just True == x} # Eta expanded, see https://github.com/ndmitchell/hlint/issues/970#issuecomment-643645053
-    - ignore: {lhs: fromMaybe True x, rhs: Just False /= x}
+    - ignore: {lhs: fromMaybe False x, rhs: Just True == x, name: Redundant fromMaybe} # Eta expanded, see https://github.com/ndmitchell/hlint/issues/970#issuecomment-643645053
+    - ignore: {lhs: fromMaybe True x, rhs: Just False /= x, name: Redundant fromMaybe}
     - warn: {lhs: not (isNothing x), rhs: isJust x}
     - warn: {lhs: not (isJust x), rhs: isNothing x}
     - warn: {lhs: "maybe [] (:[])", rhs: maybeToList}
     - warn: {lhs: catMaybes (map f x), rhs: mapMaybe f x}
+    - warn: {lhs: catMaybes (f <$> x), rhs: mapMaybe f x}
+    - warn: {lhs: catMaybes (x <&> f), rhs: mapMaybe f x}
     - warn: {lhs: catMaybes (fmap f x), rhs: mapMaybe f x}
     - hint: {lhs: case x of Nothing -> y; Just a -> a , rhs: Data.Maybe.fromMaybe y x, side: isAtom y, name: Replace case with fromMaybe}
     - hint: {lhs: case x of Just a -> a; Nothing -> y, rhs: Data.Maybe.fromMaybe y x, side: isAtom y, name: Replace case with fromMaybe}
@@ -583,6 +711,10 @@
     - warn: {lhs: if isJust x then f (fromJust x) else y, rhs: maybe y f x}
     - warn: {lhs: maybe Nothing (Just . f), rhs: fmap f}
     - hint: {lhs: map fromJust (filter isJust x), rhs:  Data.Maybe.catMaybes x}
+    - hint: {lhs: filter isJust (map f x), rhs: map Just (mapMaybe f x), name: Use mapMaybe}
+    - hint: {lhs: filter isJust (f <*> x), rhs: map Just (mapMaybe f x), name: Use mapMaybe}
+    - hint: {lhs: filter isJust (x <&> f), rhs: map Just (mapMaybe f x), name: Use mapMaybe}
+    - hint: {lhs: filter isJust (fmap f x), rhs: map Just (mapMaybe f x), name: Use mapMaybe}
     - warn: {lhs: x == Nothing , rhs:  isNothing x}
     - warn: {lhs: Nothing == x , rhs:  isNothing x}
     - warn: {lhs: x /= Nothing , rhs:  Data.Maybe.isJust x}
@@ -593,32 +725,69 @@
     - warn: {lhs: if isNothing x then y else fromJust x, rhs: fromMaybe y x}
     - warn: {lhs: if isJust x then fromJust x else y, rhs: fromMaybe y x}
     - warn: {lhs: isJust x && (fromJust x == y), rhs: x == Just y}
+    - warn: {lhs: isJust y && (x == fromJust y), rhs: Just x == y}
     - warn: {lhs: mapMaybe f (map g x), rhs: mapMaybe (f . g) x, name: Fuse mapMaybe/map}
     - warn: {lhs: fromMaybe a (fmap f x), rhs: maybe a f x}
     - warn: {lhs: fromMaybe a (f <$> x), rhs: maybe a f x}
+    - warn: {lhs: fromMaybe a (x <&> f), rhs: maybe a f x}
     - warn: {lhs: mapMaybe id, rhs: catMaybes}
-    - hint: {lhs: "[x | Just x <- a]", rhs: Data.Maybe.catMaybes a}
+    - hint: {lhs: "[x | Just x <- a]", rhs: Data.Maybe.catMaybes a, side: isVar x}
     - hint: {lhs: case m of Nothing -> Nothing; Just x -> x, rhs: Control.Monad.join m}
     - hint: {lhs: maybe Nothing id, rhs: join}
     - hint: {lhs: maybe Nothing f x, rhs: f =<< x}
+    - warn: {lhs: maybe x f (g <$> y), rhs: maybe x (f . g) y, name: Redundant <$>}
+    - warn: {lhs: maybe x f (y <&> g), rhs: maybe x (f . g) y, name: Redundant <&>}
     - warn: {lhs: maybe x f (fmap g y), rhs: maybe x (f . g) y, name: Redundant fmap}
+    - warn: {lhs: isJust (f <$> x), rhs: isJust x}
+    - warn: {lhs: isJust (x <&> f), rhs: isJust x}
     - warn: {lhs: isJust (fmap f x), rhs: isJust x}
+    - warn: {lhs: isNothing (f <$> x), rhs: isNothing x}
+    - warn: {lhs: isNothing (x <&> f), rhs: isNothing x}
     - warn: {lhs: isNothing (fmap f x), rhs: isNothing x}
+    - warn: {lhs: fromJust (f <$> x), rhs: f (fromJust x), note: IncreasesLaziness}
+    - warn: {lhs: fromJust (x <&> f), rhs: f (fromJust x), note: IncreasesLaziness}
     - warn: {lhs: fromJust (fmap f x), rhs: f (fromJust x), note: IncreasesLaziness}
+    - warn: {lhs: mapMaybe f (g <$> x), rhs: mapMaybe (f . g) x, name: Redundant <$>}
+    - warn: {lhs: mapMaybe f (x <&> g), rhs: mapMaybe (f . g) x, name: Redundant <&>}
     - warn: {lhs: mapMaybe f (fmap g x), rhs: mapMaybe (f . g) x, name: Redundant fmap}
+    - warn: {lhs: catMaybes (nub x), rhs: nub (catMaybes x), name: Move nub out}
+    - warn: {lhs: lefts (nub x), rhs: nub (lefts x), name: Move nub out}
+    - warn: {lhs: rights (nub x), rhs: nub (rights x), name: Move nub out}
+    - warn: {lhs: catMaybes (reverse x), rhs: reverse (catMaybes x), name: Move reverse out}
+    - warn: {lhs: lefts (reverse x), rhs: reverse (lefts x), name: Move reverse out}
+    - warn: {lhs: rights (reverse x), rhs: reverse (rights x), name: Move reverse out}
+    - warn: {lhs: catMaybes (sort x), rhs: sort (catMaybes x), name: Move sort out}
+    - warn: {lhs: lefts (sort x), rhs: sort (lefts x), name: Move sort out}
+    - warn: {lhs: rights (sort x), rhs: sort (rights x), name: Move sort out}
+    - warn: {lhs: catMaybes (nubOrd x), rhs: nubOrd (catMaybes x), name: Move nubOrd out}
+    - warn: {lhs: lefts (nubOrd x), rhs: nubOrd (lefts x), name: Move nubOrd out}
+    - warn: {lhs: rights (nubOrd x), rhs: nubOrd (rights x), name: Move nubOrd out}
+    - warn: {lhs: filter f (reverse x), rhs: reverse (filter f x), name: Move reverse out}
+    - warn: {lhs: mapMaybe f (reverse x), rhs: reverse (mapMaybe f x), name: Move reverse out}
 
     # EITHER
 
-    - warn: {lhs: "[a | Left a <- a]", rhs: lefts a}
-    - warn: {lhs: "[a | Right a <- a]", rhs: rights a}
+    - warn: {lhs: "[a | Left a <- b]", rhs: lefts b, side: isVar a}
+    - warn: {lhs: "[a | Right a <- b]", rhs: rights b, side: isVar a}
     - warn: {lhs: either Left (Right . f), rhs: fmap f}
     - warn: {lhs: either f g (fmap h x), rhs: either f (g . h) x, name: Redundant fmap}
+    - warn: {lhs: either f g (h <$> x), rhs: either f (g . h) x, name: Redundant <$>}
+    - warn: {lhs: either f g (x <&> h), rhs: either f (g . h) x, name: Redundant <&>}
     - warn: {lhs: isLeft (fmap f x), rhs: isLeft x}
+    - warn: {lhs: isLeft (f <$> x), rhs: isLeft x}
+    - warn: {lhs: isLeft (x <&> f), rhs: isLeft x}
     - warn: {lhs: isRight (fmap f x), rhs: isRight x}
+    - warn: {lhs: isRight (f <$> x), rhs: isRight x}
+    - warn: {lhs: isRight (x <&> f), rhs: isRight x}
     - warn: {lhs: fromLeft x (fmap f y), rhs: fromLeft x y}
+    - warn: {lhs: fromLeft x (f <$> y), rhs: fromLeft x y}
+    - warn: {lhs: fromLeft x (y <&> f), rhs: fromLeft x y}
     - warn: {lhs: fromRight x (fmap f y), rhs: either (const x) f y}
+    - warn: {lhs: fromRight x (f <$> y), rhs: either (const x) f y}
+    - warn: {lhs: fromRight x (y <&> f), rhs: either (const x) f y}
     - warn: {lhs: either (const x) id, rhs: fromRight x}
     - warn: {lhs: either id (const x), rhs: fromLeft x}
+    - warn: {lhs: either Left f x, rhs: f =<< x}
 
     # INFIX
 
@@ -656,10 +825,10 @@
     # CONCURRENT
 
     - hint: {lhs: mapM_ (writeChan a), rhs: writeList2Chan a}
-    - error: {lhs: atomically (readTVar x), rhs: readTVarIO x}
-    - error: {lhs: atomically (newTVar x), rhs: newTVarIO x}
-    - error: {lhs: atomically (newTMVar x), rhs: newTMVarIO x}
-    - error: {lhs: atomically newEmptyTMVar, rhs: newEmptyTMVarIO}
+    - warn: {lhs: atomically (readTVar x), rhs: readTVarIO x}
+    - warn: {lhs: atomically (newTVar x), rhs: newTVarIO x}
+    - warn: {lhs: atomically (newTMVar x), rhs: newTMVarIO x}
+    - warn: {lhs: atomically newEmptyTMVar, rhs: newEmptyTMVarIO}
 
     # TYPEABLE
 
@@ -700,20 +869,88 @@
 
     # FOLDABLE
 
+    - warn: {lhs: case m of Nothing -> pure (); Just x -> f x, rhs: Data.Foldable.for_ m f}
     - warn: {lhs: case m of Nothing -> return (); Just x -> f x, rhs: Data.Foldable.forM_ m f}
+    - warn: {lhs: case m of Just x -> f x; Nothing -> pure (), rhs: Data.Foldable.for_ m f}
     - warn: {lhs: case m of Just x -> f x; Nothing -> return (), rhs: Data.Foldable.forM_ m f}
+    - warn: {lhs: case m of Just x -> f x; _ -> pure (), rhs: Data.Foldable.for_ m f}
     - warn: {lhs: case m of Just x -> f x; _ -> return (), rhs: Data.Foldable.forM_ m f}
-    - warn: {lhs: when (isJust m) (f (fromJust m)), rhs: Data.Foldable.forM_ m f}
+    - warn: {lhs: when (isJust m) (f (fromJust m)), rhs: Data.Foldable.for_ m f}
+    - hint: {lhs: concatMap f (fmap g x), rhs: concatMap (f . g) x, name: Fuse concatMap/fmap}
+    - hint: {lhs: concatMap f (g <$> x), rhs: concatMap (f . g) x, name: Fuse concatMap/<$>}
+    - hint: {lhs: concatMap f (x <&> g), rhs: concatMap (f . g) x, name: Fuse concatMap/<&>}
+    - warn: {lhs: null (concatMap f x), rhs: all (null . f) x}
+    - warn: {lhs: or (concat x), rhs: any or x}
+    - warn: {lhs: or (concatMap f x), rhs: any (or . f) x}
+    - warn: {lhs: and (concat x), rhs: all and x}
+    - warn: {lhs: and (concatMap f x), rhs: all (and . f) x}
+    - warn: {lhs: any f (concat x), rhs: any (any f) x, name: Use any nested}
+    - warn: {lhs: any f (concatMap g x), rhs: any (any f . g) x, name: Use any nested}
+    - warn: {lhs: all f (concat x), rhs: all (all f) x, name: Use all nested}
+    - warn: {lhs: all f (concatMap g x), rhs: all (all f . g) x, name: Use all nested}
+    - warn: {lhs: fold (concatMap f x), rhs: foldMap (fold . f) x}
+    - warn: {lhs: foldMap f (concatMap g x), rhs: foldMap (foldMap f . g) x, name: Use foldMap nested}
+    - warn: {lhs: catMaybes (concatMap f x), rhs: concatMap (catMaybes . f) x, name: Move catMaybes}
+    - warn: {lhs: catMaybes (concat x), rhs: concatMap catMaybes x, name: Move catMaybes}
+    - hint: {lhs: filter f (concatMap g x), rhs: concatMap (filter f . g) x, name: Move filter}
+    - hint: {lhs: filter f (concat x), rhs: concatMap (filter f) x, name: Move filter}
+    - warn: {lhs: mapMaybe f (concatMap g x), rhs: concatMap (mapMaybe f . g) x, name: Move mapMaybe}
+    - warn: {lhs: mapMaybe f (concat x), rhs: concatMap (mapMaybe f) x, name: Move mapMaybe}
+    - warn: {lhs: or (fmap p x), rhs: any p x}
+    - warn: {lhs: or (p <$> x), rhs: any p x}
+    - warn: {lhs: or (x <&> p), rhs: any p x}
+    - warn: {lhs: and (fmap p x), rhs: all p x}
+    - warn: {lhs: and (p <$> x), rhs: all p x}
+    - warn: {lhs: and (x <&> p), rhs: all p x}
+    - warn: {lhs: any f (fmap g x), rhs: any (f . g) x}
+    - warn: {lhs: any f (g <$> x), rhs: any (f . g) x}
+    - warn: {lhs: any f (x <&> g), rhs: any (f . g) x}
+    - warn: {lhs: all f (fmap g x), rhs: all (f . g) x}
+    - warn: {lhs: all f (g <$> x), rhs: all (f . g) x}
+    - warn: {lhs: all f (x <&> g), rhs: all (f . g) x}
+    - hint: {lhs: foldr f z (fmap g x), rhs: foldr (f . g) z x, name: Fuse foldr/fmap}
+    - hint: {lhs: foldr f z (g <$> x), rhs: foldr (f . g) z x, name: Fuse foldr/<$>}
+    - hint: {lhs: foldr f z (x <&> g), rhs: foldr (f . g) z x, name: Fuse foldr/<&>}
+    - warn: {lhs: fold (Data.Foldable.toList x), rhs: fold x}
+    - warn: {lhs: foldMap f (Data.Foldable.toList x), rhs: foldMap f x}
+    - warn: {lhs: foldMap' f (Data.Foldable.toList x), rhs: foldMap' f x}
+    - warn: {lhs: foldr f z (Data.Foldable.toList x), rhs: foldr f z x}
+    - warn: {lhs: foldr' f z (Data.Foldable.toList x), rhs: foldr' f z x}
+    - warn: {lhs: foldl f z (Data.Foldable.toList x), rhs: foldl f z x}
+    - warn: {lhs: foldl' f z (Data.Foldable.toList x), rhs: foldl' f z x}
+    - warn: {lhs: foldr1 f (Data.Foldable.toList x), rhs: foldr1 f x}
+    - warn: {lhs: foldl1 f (Data.Foldable.toList x), rhs: foldl1 f x}
+    - warn: {lhs: null (Data.Foldable.toList x), rhs: null x}
+    - warn: {lhs: length (Data.Foldable.toList x), rhs: length x}
+    - warn: {lhs: elem y (Data.Foldable.toList x), rhs: elem y x}
+    - warn: {lhs: notElem y (Data.Foldable.toList x), rhs: notElem y x}
+    - warn: {lhs: maximum (Data.Foldable.toList x), rhs: maximum x}
+    - warn: {lhs: minimum (Data.Foldable.toList x), rhs: minimum x}
+    - warn: {lhs: maximumBy f (Data.Foldable.toList x), rhs: maximumBy f x}
+    - warn: {lhs: minimumBy f (Data.Foldable.toList x), rhs: minimumBy f x}
+    - warn: {lhs: sum (Data.Foldable.toList x), rhs: sum x}
+    - warn: {lhs: product (Data.Foldable.toList x), rhs: product x}
+    - warn: {lhs: and (Data.Foldable.toList x), rhs: and x}
+    - warn: {lhs: or (Data.Foldable.toList x), rhs: or x}
+    - warn: {lhs: all f (Data.Foldable.toList x), rhs: all f x}
+    - warn: {lhs: any f (Data.Foldable.toList x), rhs: any f x}
+    - warn: {lhs: find f (Data.Foldable.toList x), rhs: find f x}
+    - warn: {lhs: concat (Data.Foldable.toList x), rhs: concat x}
+    - warn: {lhs: concatMap f (Data.Foldable.toList x), rhs: concatMap f x}
 
     # STATE MONAD
 
     - warn: {lhs: f <$> Control.Monad.State.get, rhs: gets f}
+    - warn: {lhs: Control.Monad.State.get <&> f, rhs: gets f}
     - warn: {lhs: fmap f  Control.Monad.State.get, rhs: gets f}
     - warn: {lhs: f <$> Control.Monad.State.gets g, rhs: gets (f . g)}
+    - warn: {lhs: Control.Monad.State.gets g <&> f, rhs: gets (f . g)}
     - warn: {lhs: fmap f (Control.Monad.State.gets g), rhs: gets (f . g)}
     - warn: {lhs: f <$> Control.Monad.Reader.ask, rhs: asks f}
+    - warn: {lhs: Control.Monad.Reader.ask <&> f, rhs: asks f}
     - warn: {lhs: fmap f Control.Monad.Reader.ask, rhs: asks f}
     - warn: {lhs: f <$> Control.Monad.Reader.asks g, rhs: asks (f . g)}
+    - warn: {lhs: Control.Monad.Reader.asks g <&> f, rhs: asks (f . g)}
     - warn: {lhs: fmap f (Control.Monad.Reader.asks g), rhs: asks (f . g)}
     - warn: {lhs: fst (runState m s), rhs: evalState m s}
     - warn: {lhs: snd (runState m s), rhs: execState m s}
@@ -732,9 +969,20 @@
     - warn: {lhs: either f g (Right y), rhs: g y, name: Evaluate}
     - warn: {lhs: "fst (x,y)", rhs: x, name: Evaluate}
     - warn: {lhs: "snd (x,y)", rhs: "y", name: Evaluate}
+    - warn: {lhs: fromJust (Just x), rhs: x, name: Evaluate}
+    - warn: {lhs: fromLeft y (Left x), rhs: x, name: Evaluate}
+    - warn: {lhs: fromLeft y (Right x), rhs: "y", name: Evaluate}
+    - warn: {lhs: fromRight y (Right x), rhs: x, name: Evaluate}
+    - warn: {lhs: fromRight y (Left x), rhs: "y", name: Evaluate}
+    - warn: {lhs: "head [x]", rhs: "x", name: Evaluate}
+    - warn: {lhs: "last [x]", rhs: "x", name: Evaluate}
+    - warn: {lhs: "tail [x]", rhs: "[]", name: Evaluate}
     - warn: {lhs: "init [x]", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "null [x]", rhs: "False", name: Evaluate}
     - warn: {lhs: "null []", rhs: "True", name: Evaluate}
+    - warn: {lhs: "null \"\"", rhs: "True", name: Evaluate}
     - warn: {lhs: "length []", rhs: "0", name: Evaluate}
+    - warn: {lhs: "length \"\"", rhs: "0", name: Evaluate}
     - warn: {lhs: "foldl f z []", rhs: z, name: Evaluate}
     - warn: {lhs: "foldr f z []", rhs: z, name: Evaluate}
     - warn: {lhs: "foldr1 f [x]", rhs: x, name: Evaluate}
@@ -742,23 +990,41 @@
     - warn: {lhs: "scanr1 f []", rhs: "[]", name: Evaluate}
     - warn: {lhs: "scanr1 f [x]", rhs: "[x]", name: Evaluate}
     - warn: {lhs: "take n []", rhs: "[]", note: IncreasesLaziness, name: Evaluate}
+    - warn: {lhs: "take n \"\"", rhs: "\"\"", note: IncreasesLaziness, name: Evaluate}
     - warn: {lhs: "drop n []", rhs: "[]", note: IncreasesLaziness, name: Evaluate}
+    - warn: {lhs: "drop n \"\"", rhs: "\"\"", note: IncreasesLaziness, name: Evaluate}
     - warn: {lhs: "takeWhile p []", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "takeWhile p \"\"", rhs: "\"\"", name: Evaluate}
     - warn: {lhs: "dropWhile p []", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "dropWhile p \"\"", rhs: "\"\"", name: Evaluate}
     - warn: {lhs: "span p []", rhs: "([],[])", name: Evaluate}
-    - warn: {lhs: lines "", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "span p \"\"", rhs: "(\"\",\"\")", name: Evaluate}
+    - warn: {lhs: "lines \"\"", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "lines []", rhs: "[]", name: Evaluate}
     - warn: {lhs: "unwords []", rhs: "\"\"", name: Evaluate}
     - warn: {lhs: x - 0, rhs: x, name: Evaluate}
     - warn: {lhs: x * 1, rhs: x, name: Evaluate}
     - warn: {lhs: x / 1, rhs: x, name: Evaluate}
     - warn: {lhs: "concat [a]", rhs: a, name: Evaluate}
     - warn: {lhs: "concat []", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "concatMap f [a]", rhs: f a, name: Evaluate}
+    - warn: {lhs: "concatMap f []", rhs: "[]", name: Evaluate}
     - warn: {lhs: "zip [] []", rhs: "[]", name: Evaluate}
     - warn: {lhs: const x y, rhs: x, name: Evaluate}
     - warn: {lhs: any (const False), rhs: const False, note: IncreasesLaziness, name: Evaluate}
     - warn: {lhs: all (const True), rhs: const True, note: IncreasesLaziness, name: Evaluate}
     - warn: {lhs: "[] ++ x", rhs: x, name: Evaluate}
+    - warn: {lhs: "\"\" ++ x", rhs: x, name: Evaluate}
     - warn: {lhs: "x ++ []", rhs: x, name: Evaluate}
+    - warn: {lhs: "x ++ \"\"", rhs: x, name: Evaluate}
+    - warn: {lhs: "all f [a]", rhs: f a, name: Evaluate}
+    - warn: {lhs: "all f []", rhs: "True", name: Evaluate}
+    - warn: {lhs: "any f [a]", rhs: f a, name: Evaluate}
+    - warn: {lhs: "any f []", rhs: "False", name: Evaluate}
+    - warn: {lhs: "maximum [a]", rhs: a, name: Evaluate}
+    - warn: {lhs: "minimum [a]", rhs: a, name: Evaluate}
+    - warn: {lhs: "map f []", rhs: "[]", name: Evaluate}
+    - warn: {lhs: "map f [a]", rhs: "[f a]", name: Evaluate}
 
     # FOLDABLE + TUPLES
 
@@ -774,9 +1040,11 @@
     - warn: {lhs: "toList      (x,b)", rhs: b, name: Using toList on tuple}
     - warn: {lhs: "maximum     (x,b)", rhs: b, name: Using maximum on tuple}
     - warn: {lhs: "minimum     (x,b)", rhs: b, name: Using minimum on tuple}
+    - warn: {lhs: "notElem e   (x,b)", rhs: e /= b, name: Using notElem on tuple}
     - warn: {lhs: "sum         (x,b)", rhs: b, name: Using sum on tuple}
     - warn: {lhs: "product     (x,b)", rhs: b, name: Using product on tuple}
     - warn: {lhs: "concat      (x,b)", rhs: b, name: Using concat on tuple}
+    - warn: {lhs: "concatMap f (x,b)", rhs: f b, name: Using concatMap on tuple}
     - warn: {lhs: "and         (x,b)", rhs: b, name: Using and on tuple}
     - warn: {lhs: "or          (x,b)", rhs: b, name: Using or on tuple}
     - warn: {lhs: "any     f   (x,b)", rhs: f b, name: Using any on tuple}
@@ -794,9 +1062,11 @@
     - warn: {lhs: "toList      (x,y,b)", rhs: b, name: Using toList on tuple}
     - warn: {lhs: "maximum     (x,y,b)", rhs: b, name: Using maximum on tuple}
     - warn: {lhs: "minimum     (x,y,b)", rhs: b, name: Using minimum on tuple}
+    - warn: {lhs: "notElem e   (x,y,b)", rhs: e /= b, name: Using notElem on tuple}
     - warn: {lhs: "sum         (x,y,b)", rhs: b, name: Using sum on tuple}
     - warn: {lhs: "product     (x,y,b)", rhs: b, name: Using product on tuple}
     - warn: {lhs: "concat      (x,y,b)", rhs: b, name: Using concat on tuple}
+    - warn: {lhs: "concatMap f (x,y,b)", rhs: f b, name: Using concatMap on tuple}
     - warn: {lhs: "and         (x,y,b)", rhs: b, name: Using and on tuple}
     - warn: {lhs: "or          (x,y,b)", rhs: b, name: Using or on tuple}
     - warn: {lhs: "any     f   (x,y,b)", rhs: f b, name: Using any on tuple}
@@ -821,9 +1091,9 @@
     - warn: {lhs: "(a ^. b) ^. c", rhs: "a ^. (b . c)"}
     - warn: {lhs: "fromJust (a ^? b)", rhs: "a ^?! b"}
     - warn: {lhs: "a .~ Just b", rhs: "a ?~ b"}
-    - warn: {lhs: "a & (mapped %~ b)", rhs: "a <&> b"}
-    - warn: {lhs: "a & ((mapped . b) %~ c)", rhs: "a <&> b %~ c"}
-    - warn: {lhs: "a & (mapped .~ b)", rhs: "b <$ a"}
+    - warn: {lhs: "(mapped %~ b) a", rhs: "a <&> b"}
+    - warn: {lhs: "((mapped . b) %~ c) a", rhs: "a <&> b %~ c"}
+    - warn: {lhs: "(mapped .~ b) a", rhs: "b <$ a"}
     - warn: {lhs: "ask <&> (^. a)", rhs: "view a"}
     - warn: {lhs: "view a <&> (^. b)", rhs: "view (a . b)"}
 
@@ -831,9 +1101,9 @@
 
     - warn: {lhs: "Control.Lens.at a . Control.Lens._Just", rhs: "Control.Lens.ix a"}
     - error: {lhs: "Control.Lens.has (Control.Lens.at a)", rhs: "True"}
-    - error: {lhs: "Control.Lens.has (a . Control.Lens.at b)", rhs: "Control.Lens.has a"}
+    - warn: {lhs: "Control.Lens.has (a . Control.Lens.at b)", rhs: "Control.Lens.has a"}
     - error: {lhs: "Control.Lens.nullOf (Control.Lens.at a)", rhs: "False"}
-    - error: {lhs: "Control.Lens.nullOf (a . Control.Lens.at b)", rhs: "Control.Lens.nullOf a"}
+    - warn: {lhs: "Control.Lens.nullOf (a . Control.Lens.at b)", rhs: "Control.Lens.nullOf a"}
 
 - group:
     name: use-lens
@@ -846,6 +1116,14 @@
     - warn: {lhs: "either (const Nothing) Just", rhs: preview _Right}
 
 - group:
+    name: use-th-quotes
+    enabled: false
+    imports:
+    - package base
+    rules:
+    - hint: {lhs: "TH.varE 'a", rhs: "[|a|]", name: Use TH quotation brackets}
+
+- group:
     name: attoparsec
     enabled: true
     imports:
@@ -856,6 +1134,24 @@
     - warn: {lhs: Data.Attoparsec.ByteString.option Nothing (Just <$> p), rhs: optional p}
 
 - group:
+    name: quickcheck
+    enabled: true
+    imports:
+    - package base
+    - package quickcheck
+    rules:
+    - warn: {lhs: "Test.QuickCheck.choose (x,x)", rhs: return x}
+    - warn: {lhs: "Test.QuickCheck.chooseInt (x,x)", rhs: return x}
+    - warn: {lhs: "Test.QuickCheck.chooseInteger (x,x)", rhs: return x}
+    - warn: {lhs: "Test.QuickCheck.chooseBoundedIntegral (x,x)", rhs: return x}
+    - warn: {lhs: "Test.QuickCheck.chooseEnum (x,x)", rhs: return x}
+    - warn: {lhs: Control.Monad.join (Test.QuickCheck.elements l), rhs: Test.QuickCheck.oneof l}
+    - warn: {lhs: "Test.QuickCheck.elements [x]", rhs: "return x"}
+    - warn: {lhs: "Test.QuickCheck.growingElements [x]", rhs: "return x"}
+    - warn: {lhs: "Test.QuickCheck.oneof [x]", rhs: "x", name: Evaluate}
+    - warn: {lhs: "Test.QuickCheck.frequency [(a,x)]", rhs: "x", name: Evaluate}
+
+- group:
     name: generalise
     enabled: false
     imports:
@@ -865,7 +1161,9 @@
     - warn: {lhs: a ++ b, rhs: a <> b}
     - warn: {lhs: "sequence [a]", rhs: "pure <$> a"}
     - warn: {lhs: "x /= []", rhs: not (null x), name: Use null}
+    - warn: {lhs: "x /= \"\"", rhs: not (null x), name: Use null}
     - warn: {lhs: "[] /= x", rhs: not (null x), name: Use null}
+    - warn: {lhs: "\"\" /= x", rhs: not (null x), name: Use null}
     - warn: {lhs: "maybe []", rhs: foldMap}
 
 - group:
@@ -901,16 +1199,114 @@
     - hint: {lhs: "\\(x, y) -> [x, y]", rhs: Data.Bifoldable.biList, note: IncreasesLaziness}
     - hint: {lhs: const mempty, rhs: mempty}
     - hint: {lhs: \x -> mempty, rhs: mempty, name: Redundant lambda}
+    - warn: {lhs: map f x >>= g, rhs: x >>= g . f}
+    - warn: {lhs: g =<< map f x, rhs: g . f =<< x}
+    - hint: {lhs: join (map f x), rhs: f =<< x}
+    - warn: {lhs: map unzip (mapM f x), rhs: Control.Monad.mapAndUnzipM f x}
 
+# Warn on partial functions whose use can be avoided relatively easily.
+# These functions are in the base package, excluding IO and GHC modules.
+- group:
+    name: partial
+    enabled: false
+    imports:
+    - package base
+    rules:
+
+    # Data.Bifoldable
+
+    - warn: {lhs: bifoldr1, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: bifoldl1, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: bimaximum, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: biminimum, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: bimaximumBy, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: biminimumBy, rhs: undefined, name: Avoid partial function}
+
+    # Data.Bits
+
+    - warn: {lhs: bitSize x, rhs: "case bitSizeMaybe x of Just n -> n; Nothing -> error _", name: Avoid partial function}
+    - warn: {lhs: shiftL x b, rhs: shift x b, name: Avoid partial function}
+    - warn: {lhs: shiftR x b, rhs: shift x (-b), name: Avoid partial function}
+    - warn: {lhs: unsafeShiftL x b, rhs: shift x b, name: Avoid partial function}
+    - warn: {lhs: unsafeShiftR x b, rhs: shift x (-b), name: Avoid partial function}
+    - warn: {lhs: rotateL x b, rhs: rotate x b, name: Avoid partial function}
+    - warn: {lhs: rotateR x b, rhs: rotate x (-b), name: Avoid partial function}
+
+    # Data.Foldable
+
+    - warn: {lhs: foldr1, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: foldl1, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: maximum, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: minimum, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: maximumBy, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: minimumBy, rhs: undefined, name: Avoid partial function}
+
+    # Data.List
+
+    - warn: {lhs: head l, rhs: "case l of x:_ -> x; [] -> error _", name: Avoid partial function}
+    - warn: {lhs: last l, rhs: "case reverse l of x:_ -> x; [] -> error _", name: Avoid partial function}
+    - warn: {lhs: tail l, rhs: "case l of _:xs -> xs; [] -> error _", name: Avoid partial function}
+    - warn: {lhs: init l, rhs: "case reverse l of _:xs -> reverse xs; [] -> error _", name: Avoid partial function}
+    - warn: {lhs: l !! n, rhs: "case drop n l of x:_ -> x; [] -> error _", name: Avoid partial function}
+
+    # Data.List.NonEmpty
+
+    - warn: {lhs: Data.List.NonEmpty.fromList l, rhs: "case nonEmpty l of Just xs -> xs; Nothing -> error _", name: Avoid partial function}
+
+    # Data.Maybe
+
+    - warn: {lhs: fromJust v, rhs: "case v of Just x -> x; Nothing -> error _", name: Avoid partial function}
+
+# Strictly warn even on partial functions in the base package whose use
+# are almost unavoidable for their functionality.
+- group:
+    name: partial-strict
+    enabled: false
+    imports:
+    - package base
+    rules:
+
+    # Data.Char
+
+    - warn: {lhs: digitToInt, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: intToDigit, rhs: undefined, name: Avoid partial function}
+
+    # Data.Fixed
+
+    - warn: {lhs: div', rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: mod', rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: divMod', rhs: undefined, name: Avoid partial function}
+
+    # Data.List
+
+    - warn: {lhs: cycle, rhs: undefined, name: Avoid partial function}
+
+    # Data.Ratio
+
+    - warn: {lhs: x % y, rhs: undefined, name: Avoid partial function}
+
+    # Prelude
+
+    - warn: {lhs: succ, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: pred, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: toEnum, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: fromEnum, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: quot, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: rem, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: div, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: mod, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: quotRem, rhs: undefined, name: Avoid partial function}
+    - warn: {lhs: divMod, rhs: undefined, name: Avoid partial function}
+
 # hints that use the 'extra' library
 - group:
     name: extra
     enabled: false
     rules:
     - warn: {lhs: fmap concat (forM a b), rhs: concatForM a b}
+    - warn: {lhs: map concat (forM a b), rhs: concatForM a b}
     - warn: {lhs: concat <$> forM a b, rhs: concatForM a b}
-    - warn: {lhs: fmap concat (forM_ a b), rhs: concatForM_ a b}
-    - warn: {lhs: concat <$> forM_ a b, rhs: concatForM_ a b}
+    - warn: {lhs: forM a b <&> concat, rhs: concatForM a b}
     - warn: {lhs: "maybe (pure ()) b a", rhs: "whenJust a b"}
     - warn: {lhs: "maybe (return ()) b a", rhs: "whenJust a b"}
     - warn: {lhs: "maybeM (pure ()) b a", rhs: "whenJustM a b"}
@@ -920,16 +1316,24 @@
     - warn: {lhs: "maybeM a pure x", rhs: "fromMaybeM a b"}
     - warn: {lhs: "maybeM a return x", rhs: "fromMaybeM a b"}
     - warn: {lhs: "either a b =<< c", rhs: "eitherM a b c"}
-    - warn: {lhs: "fold1M a b >> return ()", rhs: "fold1M_ a b"}
     - warn: {lhs: "fold1M a b >> pure ()", rhs: "fold1M_ a b"}
+    - warn: {lhs: "fold1M a b >> return ()", rhs: "fold1M_ a b"}
     - warn: {lhs: "flip concatMapM", rhs: "concatForM"}
     - warn: {lhs: "liftM mconcat (mapM a b)", rhs: "mconcatMapM a b"}
+    - warn: {lhs: "ifM a b (pure ())", rhs: "whenM a b"}
     - warn: {lhs: "ifM a b (return ())", rhs: "whenM a b"}
+    - warn: {lhs: "ifM a (pure ()) b", rhs: "unlessM a b"}
     - warn: {lhs: "ifM a (return ()) b", rhs: "unlessM a b"}
+    - warn: {lhs: "ifM a (pure True) b", rhs: "(||^) a b"}
     - warn: {lhs: "ifM a (return True) b", rhs: "(||^) a b"}
+    - warn: {lhs: "ifM a b (pure False)", rhs: "(&&^) a b"}
     - warn: {lhs: "ifM a b (return False)", rhs: "(&&^) a b"}
     - warn: {lhs: "anyM id", rhs: "orM"}
     - warn: {lhs: "allM id", rhs: "andM"}
+    - warn: {lhs: "allM f [a]", rhs: f a, name: Evaluate}
+    - warn: {lhs: "allM f []", rhs: pure True, name: Evaluate}
+    - warn: {lhs: "anyM f [a]", rhs: f a, name: Evaluate}
+    - warn: {lhs: "anyM f []", rhs: pure False, name: Evaluate}
     - warn: {lhs: "either id id", rhs: "fromEither"}
     - warn: {lhs: "either (const Nothing) Just", rhs: "eitherToMaybe"}
     - warn: {lhs: "either (Left . a) Right", rhs: "mapLeft a"}
@@ -947,7 +1351,11 @@
     - warn: {lhs: "map toUpper", rhs: "upper"}
     - warn: {lhs: "mergeBy compare", rhs: "merge"}
     - warn: {lhs: "breakEnd (not . a)", rhs: "spanEnd a"}
+    - warn: {lhs: "breakEnd notNull", rhs: "spanEnd null", name: Use spanEnd}
+    - warn: {lhs: "breakEnd (notElem x)", rhs: "spanEnd (elem x)", name: Use spanEnd}
     - warn: {lhs: "spanEnd (not . a)", rhs: "breakEnd a"}
+    - warn: {lhs: "spanEnd notNull", rhs: "breakEnd null", name: Use breakEnd}
+    - warn: {lhs: "spanEnd (notElem x)", rhs: "breakEnd (elem x)", name: Use breakEnd}
     - warn: {lhs: "mconcat (map a b)", rhs: "mconcatMap a b"}
     - warn: {lhs: "fromMaybe b (stripPrefix a b)", rhs: "dropPrefix a b"}
     - warn: {lhs: "fromMaybe b (stripSuffix a b)", rhs: "dropSuffix a b"}
@@ -956,15 +1364,35 @@
     - warn: {lhs: "nubOrdBy compare", rhs: "nubOrd"}
     - warn: {lhs: "\\a -> (a, a)", rhs: "dupe"}
     - warn: {lhs: "showFFloat (Just a) b \"\"", rhs: "showDP a b"}
+    - warn: {lhs: "showFFloat (Just a) b []", rhs: "showDP a b"}
     - warn: {lhs: "readFileEncoding utf8", rhs: "readFileUTF8"}
     - warn: {lhs: "withFile a ReadMode hGetContents'", rhs: "readFile' a"}
     - warn: {lhs: "readFileEncoding' utf8", rhs: "readFileUTF8'"}
     - warn: {lhs: "withBinaryFile a ReadMode hGetContents'", rhs: "readFileBinary' a"}
     - warn: {lhs: "writeFileEncoding utf8", rhs: "writeFileUTF8"}
-    - warn: {lhs: "head $ x ++ [y]", rhs: "headDef y x"}
-    - warn: {lhs: "last $ x : y", rhs: "lastDef x y"}
+    - warn: {lhs: "head (x ++ [y])", rhs: "headDef y x"}
+    - warn: {lhs: "last (x : y)", rhs: "lastDef x y"}
     - warn: {lhs: "drop 1", rhs: "drop1"}
     - warn: {lhs: "dropEnd 1", rhs: "dropEnd1"}
+    - warn: {lhs: span notNull, rhs: break null, name: Use break}
+    - warn: {lhs: break notNull, rhs: span null, name: Use span}
+    - warn: {lhs: when (notNull x), rhs: unless (null x), name: Use unless}
+    - warn: {lhs: unless (notNull x), rhs: when (null x), name: Use when}
+    - warn: {lhs: not (notNull x), rhs: null x}
+    - warn: {lhs: notNull (concat x), rhs: any notNull x}
+    - warn: {lhs: notNull (concatMap f x), rhs: any (notNull . f) x}
+    - warn: {lhs: notNull (filter f x), rhs: any f x}
+    - warn: {lhs: "notNull [x]", rhs: "True", name: Evaluate}
+    - warn: {lhs: "notNull []", rhs: "False", name: Evaluate}
+    - warn: {lhs: "notNull \"\"", rhs: "False", name: Evaluate}
+    - hint: {lhs: "\\(x,y) -> (f x, f y)", rhs: both f}
+    - warn: {lhs: fromLeft' (Left x), rhs: x, name: Evaluate}
+    - warn: {lhs: fromRight' (Right x), rhs: x, name: Evaluate}
+    - warn: {lhs: fromEither (Left x), rhs: x, name: Evaluate}
+    - warn: {lhs: fromEither (Right x), rhs: x, name: Evaluate}
+    - hint: {lhs: nub (sort x), rhs: nubSort x}
+    - warn: {lhs: sort (nub x), rhs: nubSort x}
+    - warn: {lhs: notNull x, rhs: "True", side: isTuple x, name: Using notNull on tuple}
 
 # hints that will be enabled in future
 - group:
@@ -999,6 +1427,7 @@
     - package base
     - package codeworld-api
     rules:
+    - warn: {lhs: "pictures []", rhs: blank, name: Evaluate}
     - warn: {lhs: "pictures [ p ]", rhs: p, name: Evaluate}
     - warn: {lhs: "pictures [ p, q ]", rhs: p & q, name: Evaluate}
     - hint: {lhs: foldl1 (&), rhs: pictures}
@@ -1013,6 +1442,8 @@
     - warn: {lhs: "lighter (- a)", rhs: "darker a"}
     - warn: {lhs: "duller (- a)", rhs: "brighter a"}
     - warn: {lhs: "darker (- a)", rhs: "lighter a"}
+    - warn: {lhs: translated x y (translated u v p), rhs: translated (x + u) (y + v) p, name: Use translated once}
+    - warn: {lhs: translated 0 0 p, rhs: p}
 
 - group:
     name: teaching
@@ -1024,7 +1455,8 @@
     - hint: {lhs: "[] /= x", rhs: not (null x), name: Use null}
     - hint: {lhs: "not (x || y)", rhs: "not x && not y", name: Apply De Morgan law}
     - hint: {lhs: "not (x && y)", rhs: "not x || not y", name: Apply De Morgan law}
-    - hint: {lhs: "[ f x | x <- l ]", rhs: map f l}
+    - hint: {lhs: "[ f x | x <- l ]", rhs: map f l, side: isVar x}
+    - hint: {lhs: "[ x | x <- l, p x ]", rhs: filter p l, side: isVar x}
     - warn: {lhs: foldr f c (reverse x), rhs: foldl (flip f) c x, name: Use left fold instead of right fold}
     - warn: {lhs: foldr1 f (reverse x), rhs: foldl1 (flip f) x, name: Use left fold instead of right fold}
     - warn: {lhs: foldl f c (reverse x), rhs: foldr (flip f) c x, note: IncreasesLaziness, name: Use right fold instead of left fold}
@@ -1107,8 +1539,11 @@
 # yes = foo (elem x y) -- x `elem` y
 # no  = x `elem` y
 # no  = elem 1 [] : []
+# yes = a & (mapped . b) %~ c -- a <&> b %~ c
 # test a = foo (\x -> True) -- const True
 # test a = foo (\_ -> True) -- const True
+# {-# LANGUAGE NamedFieldPuns #-}; data Foo = Foo {foo :: Int}; issue1430_ctor x = f (\foo-> Foo{foo})
+# {-# LANGUAGE NamedFieldPuns #-}; data Foo = Foo {foo :: Int}; issue1430_update x = f (\foo -> x{foo})
 # test a = foo (\x -> x) -- id
 # h a = flip f x (y z) -- f (y z) x
 # h a = flip f x $ y z
@@ -1128,6 +1563,8 @@
 # no = x ^^ 18.5
 # instance Arrow (->) where first f = f *** id
 # yes = fromInteger 12 -- 12
+# yes = if x * y > u * v then x * y else u * v -- max (x * y) (u * v)
+# yes = scanl (\x _ -> x + 1) 7 (replicate 8 3) -- take 8 (iterate (\x -> x + 1) 7)
 # import Prelude hiding (catch); no = catch
 # import Control.Exception as E; no = E.catch
 # main = do f; putStrLn $ show x -- print x
@@ -1180,6 +1617,8 @@
 # foo = last (sortBy (compare `on` fst) xs) -- maximumBy (compare `on` fst) xs
 # g = \ f -> parseFile f >>= (\ cu -> return (f, cu))
 # foo = bar $ \(x,y) -> x x y
+# f = const [] . (>>= const Nothing) . const Nothing -- (const Nothing Control.Monad.<=< const Nothing)
+# f = g . either Left h x -- (h =<< x)
 # foo = (\x -> f x >>= g) -- f Control.Monad.>=> g
 # foo = (\f -> h f >>= g) -- h Control.Monad.>=> g
 # foo = (\f -> h f >>= f)
@@ -1188,7 +1627,7 @@
 # f condition tChar tBool = if condition then _monoField tChar else _monoField tBool
 # foo = maybe Bar{..} id -- Data.Maybe.fromMaybe Bar{..}
 # foo = (\a -> Foo {..}) 1
-# foo = zipWith SymInfo [0 ..] (repeat ty) -- map (`SymInfo` ty) [0 ..] @NoRefactor
+# foo = zipWith SymInfo [0 ..] (repeat ty) -- map (`SymInfo` ty) [0 ..]
 # foo = zipWith (SymInfo q) [0 ..] (repeat ty) -- map (( \ x_ -> SymInfo q x_ ty)) [0 .. ] @NoRefactor
 # f rec = rec
 # mean x = fst $ foldl (\(m, n) x' -> (m+(x'-m)/(n+1),n+1)) (0,0) x
@@ -1215,6 +1654,7 @@
 # issue1058 n = [] ++ issue1058 (n+1) -- issue1058 (n+1)
 # issue1183 = (a >= 'a') && a <= 'z' -- isAsciiLower a
 # issue1183 = (a >= 'a') && (a <= 'z') -- isAsciiLower a
+# issue1218 = uncurry (zipWith g) $ (a, b) -- zipWith g a b
 
 # import Prelude \
 # yes = flip mapM -- Control.Monad.forM
@@ -1243,10 +1683,23 @@
 # import Prelude((==)) \
 # import qualified Prelude as P \
 # main = P.length xs == 0 -- P.null xs
+# import Prelude () \
+# main = length xs == 0 -- null xs
+# import Prelude () \
+# import Foo \
+# main = length xs == 0 -- null xs
+# import Prelude () \
+# import Data.Text (length) \
+# main = length xs == 0
+# import Prelude () \
+# import qualified Data.Text (length) \
+# main = length xs == 0 -- null xs
 # main = hello .~ Just 12 -- hello ?~ 12
 # foo = liftIO $ window `on` deleteEvent $ do a; b
 # no = sort <$> f input `shouldBe` sort <$> x
-# sortBy (comparing length) -- sortOn length
+# no = sortBy (comparing Down)
+# yes = sortBy (flip (comparing f)) -- sortBy (comparing (Data.Ord.Down . f))
+# yes = reverse (sort x) -- sortBy (comparing Data.Ord.Down) x
 # myJoin = on $ child ^. ChildParentId ==. parent ^. ParentId
 # foo = typeOf (undefined :: Foo Int) -- typeRep (Proxy :: Proxy (Foo Int))
 # foo = typeOf (undefined :: a) -- typeRep (Proxy :: Proxy a)
diff --git a/data/import_style.yaml b/data/import_style.yaml
new file mode 100644
--- /dev/null
+++ b/data/import_style.yaml
@@ -0,0 +1,8 @@
+- modules:
+  - {name: HypotheticalModule1, as: HM1, asRequired: true}
+  - {name: HypotheticalModule2, importStyle: explicitOrQualified}
+  - {name: HypotheticalModule3, importStyle: qualified}
+  - {name: 'HypotheticalModule3.*', importStyle: unqualified}
+  - {name: 'HypotheticalModule3.OtherSubModule', importStyle: unrestricted, qualifiedStyle: post}
+  - {name: HypotheticalModule4, importStyle: qualified, as: HM4, asRequired: true}
+  - {name: HypotheticalModule5, importStyle: qualified, qualifiedStyle: post}
diff --git a/data/test-restrict.yaml b/data/test-restrict.yaml
--- a/data/test-restrict.yaml
+++ b/data/test-restrict.yaml
@@ -1,6 +1,8 @@
 - modules:
   - {name: Restricted.Module, within: []}
   - {name: Restricted.Module.Message, within: [], message: "Custom message"}
+  - {name: Restricted.Module.BadIdents, badidents: ['bad']}
+  - {name: Restricted.Module.OnlyIdents, only: ['good']}
 
 - functions:
   - {name: restricted, within: []}
diff --git a/data/wildcard.yaml b/data/wildcard.yaml
deleted file mode 100644
--- a/data/wildcard.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-- ignore: { name: Use const, within: '**.*Spec' }
diff --git a/data/wildcards.yaml b/data/wildcards.yaml
new file mode 100644
--- /dev/null
+++ b/data/wildcards.yaml
@@ -0,0 +1,31 @@
+- modules:
+  - { name: A, as: A }
+  - { name: '*B', as: B }
+  - { name: '**.C', as: C }
+  - { name: '**.*D', as: D }
+  - { name: E, within: E }
+  - { name: F, within: '*F' }
+  - { name: G, within: '**.G' }
+  - { name: H, within: '**.*H' }
+  - { name: '**.*U', within: '**.*U' }
+- ignore:
+    name: Use const
+    within:
+    - I
+    - '*J'
+    - '**.K'
+    - '**.*L'
+- extensions:
+  - name: CPP
+    within:
+    - M
+    - '*N'
+    - '**.O'
+    - '**.*P'
+- functions:
+  - name: read
+    within:
+    - Q
+    - '*R'
+    - '**.S'
+    - '**.*T'
diff --git a/hlint.cabal b/hlint.cabal
--- a/hlint.cabal
+++ b/hlint.cabal
@@ -1,13 +1,13 @@
-cabal-version:      >= 1.18
+cabal-version:      1.18
 build-type:         Simple
 name:               hlint
-version:            3.2.8
+version:            3.10
 license:            BSD3
 license-file:       LICENSE
 category:           Development
 author:             Neil Mitchell <ndmitchell@gmail.com>
 maintainer:         Neil Mitchell <ndmitchell@gmail.com>
-copyright:          Neil Mitchell 2006-2021
+copyright:          Neil Mitchell 2006-2025
 synopsis:           Source code suggestions
 description:
     HLint gives suggestions on how to improve your source code.
@@ -29,10 +29,14 @@
     data/*.hs
     data/*.yaml
     tests/*.test
+    -- These are needed because of haskell/cabal#7862
+    data/default.yaml
+    data/hlint.yaml
+    data/report_template.html
 extra-doc-files:
     README.md
     CHANGES.txt
-tested-with:        GHC==8.10, GHC==8.8, GHC==8.6
+tested-with:        GHC==9.12, GHC==9.10, GHC==9.8
 
 source-repository head
     type:     git
@@ -49,7 +53,7 @@
     description: Use GPL libraries, specifically hscolour
 
 flag ghc-lib
-  default: False
+  default: True
   manual: True
   description: Force dependency on ghc-lib-parser even if GHC API in the ghc package is supported
 
@@ -68,24 +72,25 @@
         utf8-string,
         data-default >= 0.3,
         cpphs >= 1.20.1,
-        cmdargs >= 0.10,
+        cmdargs >= 0.10.22,
         uniplate >= 1.5,
         ansi-terminal >= 0.8.1,
         extra >= 1.7.3,
         refact >= 0.3,
-        aeson >= 1.1.2.0,
+        aeson >= 1.3,
+        deriving-aeson >= 0.2,
         filepattern >= 0.1.1
 
-    if !flag(ghc-lib) && impl(ghc >= 8.10.0) && impl(ghc < 8.11.0)
+    if !flag(ghc-lib) && impl(ghc >= 9.12.1) && impl(ghc < 9.13.0)
       build-depends:
-        ghc == 8.10.*,
+        ghc == 9.12.*,
         ghc-boot-th,
         ghc-boot
     else
       build-depends:
-          ghc-lib-parser == 8.10.*
+          ghc-lib-parser == 9.12.*
     build-depends:
-        ghc-lib-parser-ex >= 8.10.0.17 && < 8.10.1
+        ghc-lib-parser-ex >= 9.12.0.0 && < 9.13.0
 
     if flag(gpl)
         build-depends: hscolour >= 1.21
@@ -119,6 +124,7 @@
         Timing
         CC
         EmbedData
+        SARIF
         Summary
         Config.Compute
         Config.Haskell
@@ -153,6 +159,7 @@
         Hint.Match
         Hint.Monad
         Hint.Naming
+        Hint.Negation
         Hint.NewType
         Hint.Pattern
         Hint.Pragma
@@ -160,10 +167,12 @@
         Hint.Smell
         Hint.Type
         Hint.Unsafe
+        Hint.NumLiteral
         Test.All
         Test.Annotations
         Test.InputOutput
         Test.Util
+    ghc-options: -Wunused-binds -Wunused-imports -Worphans -Wprepositive-qualified-module
 
 
 executable hlint
diff --git a/src/Apply.hs b/src/Apply.hs
--- a/src/Apply.hs
+++ b/src/Apply.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 
 module Apply(applyHints, applyHintFile, applyHintFiles) where
 
@@ -15,12 +16,13 @@
 import Data.Ord
 import Config.Type
 import Config.Haskell
-import SrcLoc
-import GHC.Hs
+import GHC.Types.SrcLoc
+import GHC.Hs hiding (comments)
 import Language.Haskell.GhclibParserEx.GHC.Hs
-import qualified Data.HashSet as Set
+import Data.HashSet qualified as Set
 import Prelude
-import System.FilePattern (FilePattern, (?==))
+import Util
+import Timing
 
 
 -- | Apply hints to a single file, you may have the contents of the file.
@@ -29,14 +31,14 @@
     res <- parseModuleApply flags s file src
     pure $ case res of
         Left err -> [err]
-        Right m -> executeHints s [m]
+        Right m -> timed "Execute hints" file (forceList $ executeHints s [m])
 
 
 -- | Apply hints to multiple files, allowing cross-file hints to fire.
 applyHintFiles :: ParseFlags -> [Setting] -> [FilePath] -> IO [Idea]
 applyHintFiles flags s files = do
     (err, ms) <- partitionEithers <$> mapM (\file -> parseModuleApply flags s file Nothing) files
-    pure $ err ++ executeHints s ms
+    pure $ err ++ timed "Execute hints" "all modules" (forceList $ executeHints s ms)
 
 
 -- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's.
@@ -58,8 +60,8 @@
     , let classifiers = cls ++ mapMaybe readPragma (universeBi (ghcModule m)) ++ concatMap readComment (ghcComments m)
     , seq (length classifiers) True -- to force any errors from readPragma or readComment
     , let decHints = hintDecl hints settings nm m -- partially apply
-    , let order n = map (\i -> i{ideaModule = f $ modName (ghcModule m) : ideaModule i, ideaDecl = f $ n ++ ideaDecl i}) . sortOn ideaSpan
-    , let merge = mergeBy (comparing ideaSpan)] ++
+    , let order n = map (\i -> i{ideaModule = f $ modName (ghcModule m) : ideaModule i, ideaDecl = f $ n ++ ideaDecl i}) . sortOn (SrcSpanD . ideaSpan)
+    , let merge = mergeBy (comparing (SrcSpanD . ideaSpan))] ++
     [map (classify cls) (hintModules hints settings mns)]
     where
         f = nubOrd . filter (/= "")
@@ -72,7 +74,7 @@
 removeRequiresExtensionNotes :: ModuleEx -> Idea -> Idea
 removeRequiresExtensionNotes m = \x -> x{ideaNote = filter keep $ ideaNote x}
     where
-        exts = Set.fromList $ concatMap snd $ languagePragmas $ pragmas $ ghcAnnotations m
+        exts = Set.fromList $ concatMap snd $ languagePragmas $ pragmas (comments (hsmodAnn (hsmodExt . unLoc . ghcModule $ m)))
         keep (RequiresExtension x) = not $ x `Set.member` exts
         keep _ = True
 
@@ -90,10 +92,16 @@
       Left (ParseError sl msg ctxt) ->
             pure $ Left $ classify [x | SettingClassify x <- s] $ rawIdeaN Error (adjustMessage msg) sl ctxt Nothing []
     where
+
         -- important the message has "Parse error:" as the prefix so "--ignore=Parse error" works
         -- try and tidy up things like "parse error (mismatched brackets)" to not look silly
         adjustMessage :: String -> String
-        adjustMessage x = "Parse error: " ++ dropBrackets (dropPrefix "parse error " x)
+        adjustMessage x = "Parse error: " ++ dropBrackets (
+            case stripInfix "parse error " x of
+              Nothing -> x
+              Just (prefix, _) ->
+                dropPrefix (prefix ++ "parse error ") x
+          )
 
         dropBrackets ('(':xs) | Just (xs,')') <- unsnoc xs = xs
         dropBrackets xs = xs
@@ -116,19 +124,3 @@
                 | otherwise = r
         x ~= y = x == "" || any (wildcardMatch x) y
         x  ~~= y = x == "" || x == y || ((x ++ ":") `isPrefixOf` y)
-
--- | Returns true if the pattern matches the string. For example:
---
--- >>> let isSpec = wildcardMatch "**.*Spec"
--- >>> isSpec "Example"
--- False
--- >>> isSpec "ExampleSpec"
--- True
--- >>> isSpec "Namespaced.ExampleSpec"
--- True
--- >>> isSpec "Deeply.Nested.ExampleSpec"
--- True
---
--- See this issue for details: <https://github.com/ndmitchell/hlint/issues/402>.
-wildcardMatch :: FilePattern -> String -> Bool
-wildcardMatch p m = let f = replace "." "/" in f p ?== f m
diff --git a/src/CC.hs b/src/CC.hs
--- a/src/CC.hs
+++ b/src/CC.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 -- |
@@ -14,13 +15,13 @@
 import Data.Char (toUpper)
 import Data.Text (Text)
 
-import qualified Data.Text as T
-import qualified Data.ByteString.Lazy.Char8 as C8
+import Data.Text qualified as T
+import Data.ByteString.Lazy.Char8 qualified as C8
 
 import Idea (Idea(..), Severity(..))
 
-import qualified SrcLoc as GHC
-import qualified GHC.Util as GHC
+import GHC.Types.SrcLoc qualified as GHC
+import GHC.Util qualified as GHC
 
 data Issue = Issue
     { issueType :: Text
diff --git a/src/CmdLine.hs b/src/CmdLine.hs
--- a/src/CmdLine.hs
+++ b/src/CmdLine.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost, CPP #-}
 {-# LANGUAGE PatternGuards, DeriveDataTypeable, TupleSections #-}
 {-# OPTIONS_GHC -Wno-missing-fields -fno-cse -O0 #-}
 
@@ -9,18 +10,19 @@
 
 import Control.Monad.Extra
 import Control.Exception.Extra
-import qualified Data.ByteString as BS
+import Data.ByteString qualified as BS
 import Data.Char
+import Data.List.NonEmpty qualified as NE
 import Data.List.Extra
 import Data.Maybe
 import Data.Functor
 import GHC.All(CppFlags(..))
 import GHC.LanguageExtensions.Type
 import Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx
-import DynFlags hiding (verbosity)
+import GHC.Driver.Session hiding (verbosity)
 
 import Language.Preprocessor.Cpphs
-import System.Console.ANSI(hSupportsANSIWithoutEmulation)
+import System.Console.ANSI(hSupportsANSI)
 import System.Console.CmdArgs.Explicit(helpText, HelpFormat(..))
 import System.Console.CmdArgs.Implicit
 import System.Directory.Extra
@@ -39,6 +41,7 @@
 import Paths_hlint
 import Data.Version
 import Prelude
+import Config.Type (Severity (Warning))
 
 
 getCmd :: [String] -> IO Cmd
@@ -83,7 +86,7 @@
 data ColorMode
     = Never  -- ^ Terminal output will never be coloured.
     | Always -- ^ Terminal output will always be coloured.
-    | Auto   -- ^ Terminal output will be coloured if $TERM and stdout appear to support it.
+    | Auto   -- ^ Terminal output will be coloured if $TERM and stdout appear to support it, and NO_COLOR is not set.
       deriving (Show, Typeable, Data)
 
 
@@ -95,13 +98,14 @@
     = CmdMain
         {cmdFiles :: [FilePath]    -- ^ which files to run it on, nothing = none given
         ,cmdReports :: [FilePath]        -- ^ where to generate reports
-        ,cmdGivenHints :: [FilePath]     -- ^ which settignsfiles were explicitly given
+        ,cmdGivenHints :: [FilePath]     -- ^ which settings files were explicitly given
         ,cmdWithGroups :: [String]       -- ^ groups that are given on the command line
         ,cmdGit :: Bool                  -- ^ use git ls-files to find files
         ,cmdColor :: ColorMode           -- ^ color the result
-        ,cmdThreads :: Int              -- ^ Numbmer of threads to use, 0 = whatever GHC has
+        ,cmdThreads :: Int              -- ^ Number of threads to use, 0 = whatever GHC has
         ,cmdIgnore :: [String]           -- ^ the hints to ignore
         ,cmdShowAll :: Bool              -- ^ display all skipped items
+        ,cmdIgnoreSuggestions :: Bool    -- ^ ignore suggestions
         ,cmdExtension :: [String]        -- ^ extensions
         ,cmdLanguage :: [String]      -- ^ the extensions (may be prefixed by "No")
         ,cmdCross :: Bool                -- ^ work between source files, applies to hints such as duplicate code between modules
@@ -116,6 +120,7 @@
         ,cmdCppAnsi :: Bool
         ,cmdJson :: Bool                -- ^ display hint data as JSON
         ,cmdCC :: Bool                  -- ^ display hint data as Code Climate Issues
+        ,cmdSARIF :: Bool               -- ^ display hint data as SARIF
         ,cmdNoSummary :: Bool           -- ^ do not show the summary info
         ,cmdOnly :: [String]            -- ^ specify which hints explicitly
         ,cmdNoExitCode :: Bool
@@ -125,7 +130,9 @@
         ,cmdRefactorOptions :: String   -- ^ Options to pass to the `refactor` executable.
         ,cmdWithRefactor :: FilePath    -- ^ Path to refactor tool
         ,cmdIgnoreGlob :: [FilePattern]
-        ,cmdGenerateSummary :: [FilePath]  -- ^ Generate a summary of available hints
+        ,cmdGenerateMdSummary :: [FilePath]  -- ^ Generate a summary of available hints, in Markdown format
+        ,cmdGenerateJsonSummary :: [FilePath]  -- ^ Generate a summary of built-in hints, in JSON format
+        ,cmdGenerateExhaustiveConf :: [Severity]  -- ^ Generate a hlint config file with all built-in hints set to the specified level
         ,cmdTest :: Bool
         }
     deriving (Data,Typeable,Show)
@@ -141,6 +148,7 @@
         ,cmdThreads = 1 &= name "threads" &= name "j" &= opt (0 :: Int) &= help "Number of threads to use (-j for all)"
         ,cmdIgnore = nam "ignore" &= typ "HINT" &= help "Ignore a particular hint"
         ,cmdShowAll = nam "show" &= help "Show all ignored ideas"
+        ,cmdIgnoreSuggestions = nam_ "ignore-suggestions" &= help "Ignore suggestions, only show warnings and errors"
         ,cmdExtension = nam "extension" &= typ "EXT" &= help "File extensions to search (default hs/lhs)"
         ,cmdLanguage = nam_ "language" &= name "X" &= typ "EXTENSION" &= help "Language extensions (Arrows, NoCPP)"
         ,cmdCross = nam_ "cross" &= help "Work between modules"
@@ -155,6 +163,7 @@
         ,cmdCppAnsi = nam_ "cpp-ansi" &= help "Use CPP in ANSI compatibility mode"
         ,cmdJson = nam_ "json" &= help "Display hint data as JSON"
         ,cmdCC = nam_ "cc" &= help "Display hint data as Code Climate Issues"
+        ,cmdSARIF = nam_ "sarif" &= help "Display hint data as SARIF"
         ,cmdNoSummary = nam_ "no-summary" &= help "Do not show summary information"
         ,cmdOnly = nam "only" &= typ "HINT" &= help "Specify which hints explicitly"
         ,cmdNoExitCode = nam_ "no-exit-code" &= help "Do not give a negative exit if hints"
@@ -163,8 +172,10 @@
         ,cmdRefactor = nam_ "refactor" &= help "Automatically invoke `refactor` to apply hints"
         ,cmdRefactorOptions = nam_ "refactor-options" &= typ "OPTIONS" &= help "Options to pass to the `refactor` executable"
         ,cmdWithRefactor = nam_ "with-refactor" &= help "Give the path to refactor"
-        ,cmdIgnoreGlob = nam_ "ignore-glob" &= help "Ignore paths matching glob pattern"
-        ,cmdGenerateSummary = nam_ "generate-summary" &= opt "hints.md" &= help "Generate a summary of built-in hints"
+        ,cmdIgnoreGlob = nam_ "ignore-glob" &= help "Ignore paths matching glob pattern (e.g. foo/bar/*.hs)"
+        ,cmdGenerateMdSummary = nam_ "generate-summary" &= opt "hints.md" &= help "Generate a summary of available hints, in Markdown format"
+        ,cmdGenerateJsonSummary = nam_ "generate-summary-json" &= opt "hints.json" &= help "Generate a summary of available hints, in JSON format"
+        ,cmdGenerateExhaustiveConf = nam_ "generate-config" &= opt Warning &= typ "LEVEL" &= help "Generate a .hlint.yaml config file with all hints set to the specified severity level (default level: warn, alternatives: ignore, suggest, error)"
         ,cmdTest = nam_ "test" &= help "Run the test suite"
         } &= auto &= explicit &= name "lint"
         &= details ["HLint gives hints on how to improve Haskell code."
@@ -172,9 +183,9 @@
                    ,"To check all Haskell files in 'src' and generate a report type:"
                    ,"  hlint src --report"]
     ] &= program "hlint" &= verbosity
-    &=  summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2021")
+    &=  summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2025")
     where
-        nam xs = nam_ xs &= name [head xs]
+        nam xs = nam_ xs &= name [NE.head $ NE.fromList xs]
         nam_ xs = def &= explicit &= name xs
 
 -- | Where should we find the configuration files?
@@ -188,7 +199,10 @@
         fail $ unlines $ "Failed to find requested hint files:" : map ("  "++) bad
 
     -- if the user has given any explicit hints, ignore the local ones
-    implicit <- if explicit /= [] || cmdGenerateSummary cmd /= [] then pure Nothing else do
+    implicit <- if explicit /= []
+                  || cmdGenerateMdSummary cmd /= []
+                  || cmdGenerateJsonSummary cmd /= []
+                  || cmdGenerateExhaustiveConf cmd /= [] then pure Nothing else do
         -- we follow the stylish-haskell config file search policy
         -- 1) current directory or its ancestors; 2) home directory
         curdir <- getCurrentDirectory
@@ -207,24 +221,26 @@
 cmdCpp :: Cmd -> CppFlags
 cmdCpp cmd
     | cmdCppSimple cmd = CppSimple
-    | Cpp `elem` (fst . snd) (cmdExtensions cmd) = Cpphs defaultCpphsOptions
+    | otherwise = Cpphs defaultCpphsOptions
         {boolopts=defaultBoolOptions{hashline=False, stripC89=True, ansi=cmdCppAnsi cmd}
         ,includes = cmdCppInclude cmd
         ,preInclude = cmdCppFile cmd
-        ,defines = ("__HLINT__","1") : [(a,drop1 b) | x <- cmdCppDefine cmd, let (a,b) = break (== '=') x]
+        ,defines = ("__HLINT__","1") : [(a,drop1 b) | x <- cmdCppDefine cmd, let (a,b) = break (== '=') x] ++ [("__GLASGOW_HASKELL__", show (__GLASGOW_HASKELL__ :: Int))]
         }
-    | otherwise = NoCpp
 
 
 -- | Determines whether to use colour or not.
 cmdUseColour :: Cmd -> IO Bool
-cmdUseColour cmd = case cmdColor cmd of
-  Always -> pure True
-  Never  -> pure False
-  Auto   -> do
-    supportsANSI <- hSupportsANSIWithoutEmulation stdout
-    pure $ Just True == supportsANSI
-
+cmdUseColour cmd = do
+  -- https://no-color.org
+  -- if NO_COLOR is set, regardless of value, we do not colour output.
+  noColor <- lookupEnv "NO_COLOR"
+  case cmdColor cmd of
+    Always -> pure True
+    Never  -> pure False
+    Auto   -> do
+      supportsANSI <- hSupportsANSI stdout
+      pure $ supportsANSI && isNothing noColor
 
 "." <\> x = x
 x <\> y = x </> y
@@ -257,13 +273,14 @@
     isDir <- doesDirectoryExist $ p <\> file
     if isDir then do
         let ignoredDirectories = ["dist", "dist-newstyle"]
-            avoidDir x = let y = takeFileName x in "_" `isPrefixOf` y || ("." `isPrefixOf` y && not (all (== '.') y)) || y `elem` ignoredDirectories
+            avoidDir x = let y = takeFileName x in "_" `isPrefixOf` y || ("." `isPrefixOf` y && not (all (== '.') y)) || y `elem` ignoredDirectories || ignore x
             avoidFile x = let y = takeFileName x in "." `isPrefixOf` y || ignore x
         xs <- listFilesInside (pure . not . avoidDir) $ p <\> file
         pure [x | x <- xs, drop1 (takeExtension x) `elem` exts, not $ avoidFile x]
      else do
         isFil <- doesFileExist $ p <\> file
-        if isFil then pure [p <\> file]
+        if isFil then
+            pure [p <\> file | not $ ignore $ p <\> file]
          else do
             res <- getModule p exts file
             case res of
@@ -288,23 +305,43 @@
 
 
 getExtensions :: [String] -> (Maybe Language, ([Extension], [Extension]))
-getExtensions args =
-  (lang, foldl f (if null langs then (defaultExtensions, []) else ([], [])) exts)
-    where
-        lang = if null langs then Nothing else Just $ fromJust $ lookup (last langs) ls
+getExtensions args = (lang, foldl f (startExts, []) exts)
+  where
+        -- If a language specifier is provided e.g. Haskell98 or
+        -- Haskell2010 or GHC2021, then it represents a specific set
+        -- of extensions which we default enable.
+
+        -- If no language specifier is provided we construct our own
+        -- set of extensions to default enable. The set that we
+        -- construct default enables more extensions than GHC would
+        -- default enable were it to be invoked without an explicit
+        -- language specifier given.
+        startExts :: [Extension]
+        startExts = case lang of
+          Nothing -> defaultExtensions
+          Just _ -> GHC.Driver.Session.languageExtensions lang
+
+        -- If multiple languages are given, the last language "wins".
+        lang :: Maybe Language
+        lang = fromJust . flip lookup ls . snd <$> unsnoc langs
+
+        langs, exts :: [String]
         (langs, exts) = partition (isJust . flip lookup ls) args
-        ls = [(show x, x) | x <- [Haskell98, Haskell2010]]
 
-        f (a, e) "Haskell98" = ([], [])
-        f (a, e) ('N':'o':x) | Just x <- GhclibParserEx.readExtension x, let xs = expandDisable x =
-            (deletes xs a, xs ++ deletes xs e)
+        ls :: [(String, Language)]
+        ls = [(show x, x) | x <- enumerate]
+
+        f :: ([Extension], [Extension]) -> String -> ([Extension], [Extension])
+        f (a, e) ('N':'o':x) | Just x <- GhclibParserEx.readExtension x, let xs = expandDisable x = (deletes xs a, xs ++ deletes xs e)
         f (a, e) x | Just x <- GhclibParserEx.readExtension x = (x : delete x a, delete x e)
         f (a, e) x = error $ "Unknown extension: '" ++ x ++ "'"
 
+        deletes :: [Extension] -> [Extension] -> [Extension]
         deletes [] ys = ys
-        deletes (x:xs) ys = deletes xs $ delete x ys
+        deletes (x : xs) ys = deletes xs $ delete x ys
 
         -- if you disable a feature that implies another feature, sometimes we should disable both
         -- e.g. no one knows what TemplateHaskellQuotes is https://github.com/ndmitchell/hlint/issues/1038
+        expandDisable :: Extension -> [Extension]
         expandDisable TemplateHaskell = [TemplateHaskell, TemplateHaskellQuotes]
         expandDisable x = [x]
diff --git a/src/Config/Compute.hs b/src/Config/Compute.hs
--- a/src/Config/Compute.hs
+++ b/src/Config/Compute.hs
@@ -10,10 +10,9 @@
 import Fixity
 import Data.Generics.Uniplate.DataOnly
 import GHC.Hs hiding (Warning)
-import RdrName
-import Name
-import Bag
-import SrcLoc
+import GHC.Types.Name.Reader
+import GHC.Types.Name
+import GHC.Types.SrcLoc
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
@@ -43,40 +42,40 @@
     ["- fixity: " ++ show (unsafePrettyPrint $ toFixitySig x)]
 renderSetting _ = []
 
-findSetting :: LHsDecl GhcPs -> [Setting]
+findSetting :: LocatedA (HsDecl GhcPs) -> [Setting]
 findSetting (L _ (ValD _ x)) = findBind x
 findSetting (L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) =
-    concatMap (findBind . unLoc) $ bagToList cid_binds
+    concatMap (findBind . unLoc) cid_binds
 findSetting (L _ (SigD _ (FixSig _ x))) = map Infix $ fromFixitySig x
 findSetting x = []
 
 
 findBind :: HsBind GhcPs -> [Setting]
 findBind VarBind{var_id, var_rhs} = findExp var_id [] $ unLoc var_rhs
-findBind FunBind{fun_id, fun_matches} = findExp (unLoc fun_id) [] $ HsLam noExtField fun_matches
+findBind FunBind{fun_id, fun_matches} = findExp (unLoc fun_id) [] $ HsLam noAnn LamSingle fun_matches
 findBind _ = []
 
 findExp :: IdP GhcPs -> [String] -> HsExpr GhcPs -> [Setting]
-findExp name vs (HsLam _ MG{mg_alts=L _ [L _ Match{m_pats, m_grhss=GRHSs{grhssGRHSs=[L _ (GRHS _ [] x)], grhssLocalBinds=L _ (EmptyLocalBinds _)}}]})
-    = if length m_pats == length ps then findExp name (vs++ps) $ unLoc x else []
-    where ps = [rdrNameStr x | L _ (VarPat _ x) <- m_pats]
+findExp name vs (HsLam _ LamSingle MG{mg_alts=L _ [L _ Match{m_pats=L _ pats, m_grhss=GRHSs{grhssGRHSs=[L _ (GRHS _ [] x)], grhssLocalBinds=(EmptyLocalBinds _)}}]})
+    = if length pats == length ps then findExp name (vs++ps) $ unLoc x else []
+    where ps = [rdrNameStr x | L _ (VarPat _ x) <- pats]
 findExp name vs HsLam{} = []
 findExp name vs HsVar{} = []
 findExp name vs (OpApp _ x dot y) | isDot dot = findExp name (vs++["_hlint"]) $
-    HsApp noExtField x $ noLoc $ HsPar noExtField $ noLoc $ HsApp noExtField y $ noLoc $ mkVar "_hlint"
+    HsApp noExtField x $ nlHsPar $ noLocA $ HsApp noExtField y $ noLocA $ mkVar "_hlint"
 
 findExp name vs bod = [SettingMatchExp $
         HintRule Warning defaultHintName []
         mempty (extendInstances lhs) (extendInstances $ fromParen rhs) Nothing]
     where
-        lhs = fromParen $ noLoc $ transform f bod
-        rhs = apps $ map noLoc $ HsVar noExtField (noLoc name) : map snd rep
+        lhs = fromParen $ noLocA $ transform f bod
+        rhs = apps $ map noLocA $ HsVar noExtField (noLocA name) : map snd rep
 
         rep = zip vs $ map (mkVar . pure) ['a'..]
         f (HsVar _ x) | Just y <- lookup (rdrNameStr x) rep = y
-        f (OpApp _ x dol y) | isDol dol = HsApp noExtField x $ noLoc $ HsPar noExtField y
+        f (OpApp _ x dol y) | isDol dol = HsApp noExtField x $ nlHsPar y
         f x = x
 
 
 mkVar :: String -> HsExpr GhcPs
-mkVar = HsVar noExtField . noLoc . Unqual . mkVarOcc
+mkVar = HsVar noExtField . noLocA . Unqual . mkVarOcc
diff --git a/src/Config/Haskell.hs b/src/Config/Haskell.hs
--- a/src/Config/Haskell.hs
+++ b/src/Config/Haskell.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE PatternGuards, ViewPatterns, TupleSections #-}
 module Config.Haskell(
     readPragma,
@@ -15,14 +16,15 @@
 
 import GHC.Util
 
-import SrcLoc
+import GHC.Types.SrcLoc
 import GHC.Hs.Extension
 import GHC.Hs.Decls hiding (SpliceDecl)
 import GHC.Hs.Expr hiding (Match)
 import GHC.Hs.Lit
-import FastString
-import ApiAnnotation
-import Outputable
+import GHC.Data.FastString
+import GHC.Parser.Annotation
+import GHC.Utils.Outputable
+import GHC.Data.Strict qualified
 
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
@@ -30,13 +32,14 @@
 -- | Read an {-# ANN #-} pragma and determine if it is intended for HLint.
 --   Return Nothing if it is not an HLint pragma, otherwise what it means.
 readPragma :: AnnDecl GhcPs -> Maybe Classify
-readPragma (HsAnnotation _ _ provenance expr) = f expr
+readPragma (HsAnnotation _ provenance expr) = f expr
     where
         name = case provenance of
             ValueAnnProvenance (L _ x) -> occNameStr x
             TypeAnnProvenance (L _ x) -> occNameStr x
             ModuleAnnProvenance -> ""
 
+        f :: LocatedA (HsExpr GhcPs) -> Maybe Classify
         f (L _ (HsLit _ (HsString _ (unpackFS -> s)))) | "hlint:" `isPrefixOf` lower s =
                 case getSeverity a of
                     Nothing -> errorOn expr "bad classify pragma"
@@ -45,10 +48,9 @@
         f (L _ (HsPar _ x)) = f x
         f (L _ (ExprWithTySig _ x _)) = f x
         f _ = Nothing
-readPragma _ = Nothing
 
-readComment :: Located AnnotationComment -> [Classify]
-readComment c@(L pos AnnBlockComment{})
+readComment :: LEpaComment -> [Classify]
+readComment c@(L pos (EpaComment EpaBlockComment{} _))
     | (hash, x) <- maybe (False, x) (True,) $ stripPrefix "#" x
     , x <- trim x
     , (hlint, x) <- word1 x
@@ -74,15 +76,15 @@
 readComment _ = []
 
 
-errorOn :: Outputable a => Located a -> String -> b
+errorOn :: Outputable a => LocatedA a -> String -> b
 errorOn (L pos val) msg = exitMessageImpure $
-    showSrcSpan pos ++
+    showSrcSpan (locA pos) ++
     ": Error while reading hint file, " ++ msg ++ "\n" ++
     unsafePrettyPrint val
 
-errorOnComment :: Located AnnotationComment -> String -> b
+errorOnComment :: LEpaComment -> String -> b
 errorOnComment c@(L s _) msg = exitMessageImpure $
     let isMultiline = isCommentMultiline c in
-    showSrcSpan s ++
+    showSrcSpan (RealSrcSpan (epaLocationRealSrcSpan s) GHC.Data.Strict.Nothing) ++
     ": Error while reading hint file, " ++ msg ++ "\n" ++
     (if isMultiline then "{-" else "--") ++ commentText c ++ (if isMultiline then "-}" else "")
diff --git a/src/Config/Type.hs b/src/Config/Type.hs
--- a/src/Config/Type.hs
+++ b/src/Config/Type.hs
@@ -1,19 +1,31 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Config.Type(
     Severity(..), Classify(..), HintRule(..), Note(..), Setting(..),
-    Restrict(..), RestrictType(..), SmellType(..),
+    Restrict(..), RestrictType(..), RestrictIdents(..), SmellType(..),
+    RestrictImportStyle(..), QualifiedStyle(..),
     defaultHintName, isUnifyVar, showNotes, getSeverity, getRestrictType, getSmellType
     ) where
 
 import Data.Char
 import Data.List.Extra
+import Data.Monoid
 import Prelude
 
 
-import qualified GHC.Hs
+import GHC.Hs qualified
 import Fixity
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
+import Deriving.Aeson
+import System.Console.CmdArgs.Implicit
+import Data.Aeson hiding (Error)
 
 getSeverity :: String -> Maybe Severity
 getSeverity "ignore" = Just Ignore
@@ -42,7 +54,8 @@
     | Suggestion -- ^ Suggestions are things that some people may consider improvements, but some may not.
     | Warning -- ^ Warnings are suggestions that are nearly always a good idea to apply.
     | Error -- ^ Available as a setting for the user. Only parse errors have this setting by default.
-      deriving (Eq,Ord,Show,Read,Bounded,Enum)
+      deriving (Eq,Ord,Show,Read,Bounded,Enum,Generic,Data)
+      deriving (ToJSON) via CustomJSON '[FieldLabelModifier CamelToSnake] Severity
 
 
 -- Any 1-letter variable names are assumed to be unification variables
@@ -104,15 +117,52 @@
     }
     deriving Show
 
+instance ToJSON HintRule where
+    toJSON HintRule{..} = object
+        [ "name" .= hintRuleName
+        , "lhs" .= show hintRuleLHS
+        , "rhs" .= show hintRuleRHS
+        ]
+
 data RestrictType = RestrictModule | RestrictExtension | RestrictFlag | RestrictFunction deriving (Show,Eq,Ord)
 
+data RestrictIdents
+    = NoRestrictIdents -- No restrictions on module imports
+    | ForbidIdents [String] -- Forbid importing the given identifiers from this module
+    | OnlyIdents [String] -- Forbid importing all identifiers from this module, except the given identifiers
+    deriving Show
+
+instance Semigroup RestrictIdents where
+    NoRestrictIdents <> ri = ri
+    ri <> NoRestrictIdents = ri
+    ForbidIdents x1 <> ForbidIdents y1 = ForbidIdents $ x1 <> y1
+    OnlyIdents x1 <> OnlyIdents x2 = OnlyIdents $ x1 <> x2
+    ri1 <> ri2 = error $ "Incompatible restrictions: " ++ show (ri1, ri2)
+
+data RestrictImportStyle
+  = ImportStyleQualified
+  | ImportStyleUnqualified
+  | ImportStyleExplicit
+  | ImportStyleExplicitOrQualified
+  | ImportStyleUnrestricted
+  deriving Show
+
+data QualifiedStyle
+  = QualifiedStylePre
+  | QualifiedStylePost
+  | QualifiedStyleUnrestricted
+  deriving Show
+
 data Restrict = Restrict
     {restrictType :: RestrictType
     ,restrictDefault :: Bool
     ,restrictName :: [String]
     ,restrictAs :: [String] -- for RestrictModule only, what module names you can import it as
+    ,restrictAsRequired :: Alt Maybe Bool -- for RestrictModule only
+    ,restrictImportStyle :: Alt Maybe RestrictImportStyle -- for RestrictModule only
+    ,restrictQualifiedStyle :: Alt Maybe QualifiedStyle -- for RestrictModule only
     ,restrictWithin :: [(String, String)]
-    ,restrictBadIdents :: [String]
+    ,restrictIdents :: RestrictIdents -- for RestrictModule only, what identifiers can be imported from it
     ,restrictMessage :: Maybe String
     } deriving Show
 
diff --git a/src/Config/Yaml.hs b/src/Config/Yaml.hs
--- a/src/Config/Yaml.hs
+++ b/src/Config/Yaml.hs
@@ -1,46 +1,65 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedStrings, ViewPatterns, RecordWildCards, GeneralizedNewtypeDeriving, TupleSections #-}
-{-# LANGUAGE CPP #-}
 
 module Config.Yaml(
     ConfigYaml,
+    ConfigYamlBuiltin (..),
+    ConfigYamlUser (..),
     readFileConfigYaml,
-    settingsFromConfigYaml
+    settingsFromConfigYaml,
+    isBuiltinYaml,
     ) where
 
+#if defined(MIN_VERSION_aeson)
+#if MIN_VERSION_aeson(2,0,0)
+#define AESON 2
+#else
+#define AESON 1
+#endif
+#else
+#define AESON 2
+#endif
+
+import GHC.Driver.Ppr
+import GHC.Driver.Errors.Types
+import GHC.Types.Error hiding (Severity)
+
 import Config.Type
-import Data.Either
+import Data.Either.Extra
 import Data.Maybe
+import Data.List.NonEmpty qualified as NE
 import Data.List.Extra
 import Data.Tuple.Extra
 import Control.Monad.Extra
-import qualified Data.Text as T
-import qualified Data.Vector as V
-import qualified Data.ByteString.Char8 as BS
-import qualified Data.HashMap.Strict as Map
+import Data.Text qualified as T
+import Data.Vector qualified as V
+import Data.ByteString.Char8 qualified as BS
+import Data.HashMap.Strict qualified as Map
 import Data.Generics.Uniplate.DataOnly
 import GHC.All
 import Fixity
 import Extension
-import Module
+import GHC.Unit.Module
 import Data.Functor
+import Data.Monoid
 import Data.Semigroup
 import Timing
 import Prelude
 
-import Bag
-import Lexer
-import ErrUtils hiding (Severity)
-import Outputable
+import GHC.Data.Bag
+import GHC.Parser.Lexer
+import GHC.Utils.Error hiding (Severity)
 import GHC.Hs
-import SrcLoc
-import RdrName
-import OccName
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
 import GHC.Util (baseDynFlags, Scope, scopeCreate)
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import Data.Char
-#if MIN_VERSION_aeson(2,0,0)
+#if AESON == 2
 import Data.Aeson.KeyMap (toHashMapText)
 #endif
 
@@ -50,7 +69,7 @@
 import Data.YAML.Aeson (encode1Strict, decode1Strict)
 import Data.Aeson hiding (encode)
 import Data.Aeson.Types (Parser)
-import qualified Data.ByteString as BSS
+import Data.ByteString qualified as BSS
 
 decodeFileEither :: FilePath -> IO (Either (Pos, String) ConfigYaml)
 decodeFileEither path = decode1Strict <$> BSS.readFile path
@@ -71,7 +90,7 @@
 
 #endif
 
-#if !MIN_VERSION_aeson(2,0,0)
+#if AESON == 1
 toHashMapText :: a -> a
 toHashMapText = id
 #endif
@@ -81,12 +100,20 @@
 readFileConfigYaml :: FilePath -> Maybe String -> IO ConfigYaml
 readFileConfigYaml file contents = timedIO "Config" file $ do
     val <- case contents of
-        Nothing -> decodeFileEither file
-        Just src -> pure $ decodeEither' $ BS.pack src
+        Nothing ->
+            if isBuiltinYaml file
+                then mapRight getConfigYamlBuiltin <$> decodeFileEither file
+                else mapRight getConfigYamlUser <$> decodeFileEither file
+        Just src -> pure $
+            if isBuiltinYaml file
+                then mapRight getConfigYamlBuiltin $ decodeEither' $ BS.pack src
+                else mapRight getConfigYamlUser $ decodeEither' $ BS.pack src
     case val of
         Left e -> fail $ "Failed to read YAML configuration file " ++ file ++ "\n  " ++ displayException e
         Right v -> pure v
 
+isBuiltinYaml :: FilePath -> Bool
+isBuiltinYaml = (== "data/hlint.yaml")
 
 ---------------------------------------------------------------------
 -- YAML DATA TYPE
@@ -137,7 +164,7 @@
     -- aim to show a smallish but relevant context
     dotDot (fromMaybe (encode focus) $ listToMaybe $ dropWhile (\x -> BS.length x > 250) $ map encode contexts)
     where
-        (steps, contexts) = unzip $ reverse path
+        (steps, contexts) = Prelude.unzip $ reverse path
         dotDot x = let (a,b) = BS.splitAt 250 x in BS.unpack a ++ (if BS.null b then "" else "...")
 
 parseArray :: Val -> Parser [Val]
@@ -170,6 +197,12 @@
 maybeParse parseValue Nothing = pure Nothing
 maybeParse parseValue (Just value) = Just <$> parseValue value
 
+maybeParseEnum :: [(T.Text, a)] -> Maybe Val -> Parser (Maybe a)
+maybeParseEnum _ Nothing = pure Nothing
+maybeParseEnum dict (Just val) = case getVal val of
+  String str | Just x <- lookup str dict -> pure $ Just x
+  _ -> parseFail val . T.unpack $ "expected '" <> T.intercalate "', '" (fst <$> dict) <> "'"
+
 parseBool :: Val -> Parser Bool
 parseBool (getVal -> Bool b) = pure b
 parseBool v = parseFail v "Expected a Bool"
@@ -203,30 +236,39 @@
     case parser defaultParseFlags{enabledExtensions=configExtensions, disabledExtensions=[]} x of
         POk _ x -> pure x
         PFailed ps ->
-          let (_, errs) = getMessages ps baseDynFlags
-              errMsg = head (bagToList errs)
-              msg = Outputable.showSDoc baseDynFlags $ ErrUtils.pprLocErrMsg errMsg
+          let errMsg = NE.head . NE.fromList . bagToList . getMessages $ GhcPsMessage <$> snd (getPsMessages ps)
+              msg = showSDoc baseDynFlags $ pprLocMsgEnvelopeDefault errMsg
           in parseFail v $ "Failed to parse " ++ msg ++ ", when parsing:\n " ++ x
 
 ---------------------------------------------------------------------
 -- YAML TO DATA TYPE
 
-instance FromJSON ConfigYaml where
+newtype ConfigYamlBuiltin = ConfigYamlBuiltin { getConfigYamlBuiltin :: ConfigYaml }
+  deriving (Semigroup, Monoid)
+
+newtype ConfigYamlUser = ConfigYamlUser { getConfigYamlUser :: ConfigYaml }
+  deriving (Semigroup, Monoid)
+
+instance FromJSON ConfigYamlBuiltin where
     parseJSON Null = pure mempty
-    parseJSON x = parseConfigYaml $ newVal x
+    parseJSON x = ConfigYamlBuiltin <$> parseConfigYaml True (newVal x)
 
-parseConfigYaml :: Val -> Parser ConfigYaml
-parseConfigYaml v = do
+instance FromJSON ConfigYamlUser where
+  parseJSON Null = pure mempty
+  parseJSON x = ConfigYamlUser <$> parseConfigYaml False (newVal x)
+
+parseConfigYaml :: Bool -> Val -> Parser ConfigYaml
+parseConfigYaml isBuiltin v = do
     vs <- parseArray v
     fmap ConfigYaml $ forM vs $ \o -> do
         (s, v) <- parseObject1 o
         case s of
             "package" -> ConfigPackage <$> parsePackage v
-            "group" -> ConfigGroup <$> parseGroup v
+            "group" -> ConfigGroup <$> parseGroup isBuiltin v
             "arguments" -> ConfigSetting . map SettingArgument <$> parseArrayString v
             "fixity" -> ConfigSetting <$> parseFixity v
             "smell" -> ConfigSetting <$> parseSmell v
-            _ | isJust $ getSeverity s -> ConfigGroup . ruleToGroup <$> parseRule o
+            _ | isJust $ getSeverity s -> ConfigGroup . ruleToGroup <$> parseRule isBuiltin o
             _ | Just r <- getRestrictType s -> ConfigSetting . map SettingRestrict <$> (parseArray v >>= mapM (parseRestrict r))
             _ -> parseFail v "Expecting an object with a 'package' or 'group' key, a hint or a restriction"
 
@@ -255,12 +297,12 @@
       require _ _ (Just a) = pure a
       require val err Nothing = parseFail val err
 
-parseGroup :: Val -> Parser Group
-parseGroup v = do
+parseGroup :: Bool -> Val -> Parser Group
+parseGroup isBuiltin v = do
     groupName <- parseField "name" v >>= parseString
     groupEnabled <- parseFieldOpt "enabled" v >>= maybe (pure True) parseBool
     groupImports <- parseFieldOpt "imports" v >>= maybe (pure []) (parseArray >=> mapM parseImport)
-    groupRules <- parseFieldOpt "rules" v >>= maybe (pure []) parseArray >>= concatMapM parseRule
+    groupRules <- parseFieldOpt "rules" v >>= maybe (pure []) parseArray >>= concatMapM (parseRule isBuiltin)
     allowFields v ["name","enabled","imports","rules"]
     pure Group{..}
     where
@@ -273,8 +315,8 @@
 ruleToGroup :: [Either HintRule Classify] -> Group
 ruleToGroup = Group "" True []
 
-parseRule :: Val -> Parser [Either HintRule Classify]
-parseRule v = do
+parseRule :: Bool -> Val -> Parser [Either HintRule Classify]
+parseRule isBuiltin v = do
     (severity, v) <- parseSeverityKey v
     isRule <- isJust <$> parseFieldOpt "lhs" v
     if isRule then do
@@ -286,7 +328,9 @@
 
         allowFields v ["lhs","rhs","note","name","side"]
         let hintRuleScope = mempty
-        pure [Left HintRule{hintRuleSeverity=severity,hintRuleLHS=extendInstances lhs,hintRuleRHS=extendInstances rhs, ..}]
+        pure $
+          Left HintRule {hintRuleSeverity = severity, hintRuleLHS = extendInstances lhs, hintRuleRHS = extendInstances rhs, ..}
+            : [Right $ Classify severity hintRuleName "" "" | not isBuiltin]
      else do
         names <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString
         within <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin)
@@ -299,14 +343,41 @@
         Just def -> do
             b <- parseBool def
             allowFields v ["default"]
-            pure $ Restrict restrictType b [] [] [] [] Nothing
+            pure $ Restrict restrictType b [] mempty mempty mempty mempty [] NoRestrictIdents Nothing
         Nothing -> do
             restrictName <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString
             restrictWithin <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin)
             restrictAs <- parseFieldOpt "as" v >>= maybe (pure []) parseArrayString
-            restrictBadIdents <- parseFieldOpt "badidents" v >>= maybe (pure []) parseArrayString
+            restrictAsRequired <- parseFieldOpt "asRequired" v >>= fmap Alt . maybeParse parseBool
+            restrictImportStyle <- parseFieldOpt "importStyle" v >>= fmap Alt . maybeParseEnum
+              [ ("qualified"          , ImportStyleQualified)
+              , ("unqualified"        , ImportStyleUnqualified)
+              , ("explicit"           , ImportStyleExplicit)
+              , ("explicitOrQualified", ImportStyleExplicitOrQualified)
+              , ("unrestricted"       , ImportStyleUnrestricted)
+              ]
+            restrictQualifiedStyle <- parseFieldOpt "qualifiedStyle" v >>= fmap Alt . maybeParseEnum
+              [ ("pre"         , QualifiedStylePre)
+              , ("post"        , QualifiedStylePost)
+              , ("unrestricted", QualifiedStyleUnrestricted)
+              ]
+
+
+            restrictBadIdents <- parseFieldOpt "badidents" v
+            restrictOnlyAllowedIdents <- parseFieldOpt "only" v
+            restrictIdents <-
+                case (restrictBadIdents, restrictOnlyAllowedIdents) of
+                    (Just badIdents, Nothing) -> ForbidIdents <$> parseArrayString badIdents
+                    (Nothing, Just onlyIdents) -> OnlyIdents <$> parseArrayString onlyIdents
+                    (Nothing, Nothing) -> pure NoRestrictIdents
+                    _ -> parseFail v "The following options are mutually exclusive: badidents, only"
+
             restrictMessage <- parseFieldOpt "message" v >>= maybeParse parseString
-            allowFields v $ ["as" | restrictType == RestrictModule] ++ ["badidents", "name", "within", "message"]
+            allowFields v $
+                ["name", "within", "message"] ++
+                if restrictType == RestrictModule
+                    then ["as", "asRequired", "importStyle", "qualifiedStyle", "badidents", "only"]
+                    else []
             pure Restrict{restrictDefault=True,..}
 
 parseWithin :: Val -> Parser [(String, String)] -- (module, decl)
@@ -370,8 +441,8 @@
             where
               scope'= asScope' packageMap' (map (fmap unextendInstances) groupImports)
 
-asScope' :: Map.HashMap String [LImportDecl GhcPs] -> [Either String (LImportDecl GhcPs)] -> Scope
-asScope' packages xs = scopeCreate (HsModule Nothing Nothing (concatMap f xs) [] Nothing Nothing)
+asScope' :: Map.HashMap String [LocatedA (ImportDecl GhcPs)] -> [Either String (LocatedA (ImportDecl GhcPs))] -> Scope
+asScope' packages xs = scopeCreate (HsModule (XModulePs noAnn EpNoLayout Nothing Nothing) Nothing Nothing (concatMap f xs) [])
     where
         f (Right x) = [x]
         f (Left x) | Just pkg <- Map.lookup x packages = pkg
diff --git a/src/EmbedData.hs b/src/EmbedData.hs
--- a/src/EmbedData.hs
+++ b/src/EmbedData.hs
@@ -10,11 +10,16 @@
 import Data.ByteString.UTF8
 import Data.FileEmbed
 
+-- Use NOINLINE below to avoid dirtying too much when these files change
+
+{-# NOINLINE hlintYaml #-}
 hlintYaml :: (FilePath, Maybe String)
 hlintYaml = ("data/hlint.yaml", Just $ toString $(embedFile "data/hlint.yaml"))
 
+{-# NOINLINE defaultYaml #-}
 defaultYaml :: String
 defaultYaml = toString $(embedFile "data/default.yaml")
 
+{-# NOINLINE reportTemplate #-}
 reportTemplate :: String
 reportTemplate = toString $(embedFile "data/report_template.html")
diff --git a/src/Extension.hs b/src/Extension.hs
--- a/src/Extension.hs
+++ b/src/Extension.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 module Extension(
   defaultExtensions,
   configExtensions,
@@ -6,9 +7,9 @@
   ) where
 
 import Data.List.Extra
-import qualified Data.Map as Map
+import Data.Map qualified as Map
 import GHC.LanguageExtensions.Type
-import qualified Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session qualified as GhclibParserEx
 
 badExtensions =
   reallyBadExtensions ++
@@ -16,6 +17,10 @@
   , UnboxedTuples, UnboxedSums -- breaks (#) lens operator
   , QuasiQuotes -- breaks [x| ...], making whitespace free list comps break
   , {- DoRec , -} RecursiveDo -- breaks rec
+  , LexicalNegation -- changes '-', see https://github.com/ndmitchell/hlint/issues/1230
+  -- These next two change syntax significantly and must be opt-in.
+  , OverloadedRecordDot
+  , OverloadedRecordUpdate
   ]
 
 reallyBadExtensions =
@@ -26,6 +31,7 @@
   , NegativeLiterals -- Was not enabled by HSE and enabling breaks tests.
   , StarIsType -- conflicts with TypeOperators. StarIsType is currently enabled by default,
                -- so adding it here has no effect, but it may not be the case in future GHC releases.
+  , MonadComprehensions -- Discussed in https://github.com/ndmitchell/hlint/issues/1261
   ]
 
 -- | Extensions we turn on by default when parsing. Aim to parse as
diff --git a/src/Fixity.hs b/src/Fixity.hs
--- a/src/Fixity.hs
+++ b/src/Fixity.hs
@@ -9,10 +9,10 @@
 import GHC.Generics(Associativity(..))
 import GHC.Hs.Binds
 import GHC.Hs.Extension
-import OccName
-import RdrName
-import SrcLoc
-import BasicTypes
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name.Reader
+import GHC.Types.Fixity
+import GHC.Parser.Annotation
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import Language.Haskell.GhclibParserEx.Fixity
 
@@ -27,23 +27,22 @@
 type FixityInfo = (String, Associativity, Int)
 
 fromFixitySig :: FixitySig GhcPs -> [FixityInfo]
-fromFixitySig (FixitySig _ names (Fixity _ i dir)) =
+fromFixitySig (FixitySig _ names (Fixity i dir)) =
     [(rdrNameStr name, f dir, i) | name <- names]
     where
         f InfixL = LeftAssociative
         f InfixR = RightAssociative
         f InfixN = NotAssociative
-fromFixitySig _ = []
 
 toFixity :: FixityInfo -> (String, Fixity)
-toFixity (name, dir, i) = (name, Fixity NoSourceText i $ f dir)
+toFixity (name, dir, i) = (name, Fixity i $ f dir)
     where
         f LeftAssociative = InfixL
         f RightAssociative = InfixR
         f NotAssociative = InfixN
 
 fromFixity :: (String, Fixity) -> FixityInfo
-fromFixity (name, Fixity _ i dir) = (name, assoc dir, i)
+fromFixity (name, Fixity i dir) = (name, assoc dir, i)
   where
     assoc dir = case dir of
       InfixL -> LeftAssociative
@@ -51,7 +50,7 @@
       InfixN -> NotAssociative
 
 toFixitySig :: FixityInfo -> FixitySig GhcPs
-toFixitySig (toFixity -> (name, x)) = FixitySig noExtField [noLoc $ mkRdrUnqual (mkVarOcc name)] x
+toFixitySig (toFixity -> (name, x)) = FixitySig NoNamespaceSpecifier [noLocA $ mkRdrUnqual (mkVarOcc name)] x
 
 defaultFixities :: [FixityInfo]
 defaultFixities = map fromFixity $ customFixities ++ baseFixities ++ lensFixities ++ otherFixities
diff --git a/src/GHC/All.hs b/src/GHC/All.hs
--- a/src/GHC/All.hs
+++ b/src/GHC/All.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE ViewPatterns #-}
 
@@ -5,41 +6,47 @@
     CppFlags(..), ParseFlags(..), defaultParseFlags,
     parseFlagsAddFixities, parseFlagsSetLanguage,
     ParseError(..), ModuleEx(..),
-    parseModuleEx, createModuleEx, ghcComments,
+    parseModuleEx, createModuleEx, createModuleExWithFixities, ghcComments, modComments, firstDeclComments,
     parseExpGhcWithMode, parseImportDeclGhcWithMode, parseDeclGhcWithMode,
     ) where
 
+import GHC.Driver.Ppr
 import Control.Monad.Trans.Except
 import Control.Monad.IO.Class
 import Util
 import Data.Char
+import Data.List
+import Data.List.NonEmpty qualified as NE
 import Data.List.Extra
 import Timing
 import Language.Preprocessor.Cpphs
-import qualified Data.Map as Map
 import System.IO.Extra
 import Fixity
 import Extension
-import FastString
+import GHC.Data.FastString
 
-import GHC.Hs
-import SrcLoc
-import ErrUtils
-import Outputable
-import Lexer hiding (context)
+import GHC.Hs hiding (comments)
+import GHC.Types.SrcLoc
+import GHC.Types.Fixity
+import GHC.Types.Error
+import GHC.Driver.Errors.Types
+
+import GHC.Utils.Error
+import GHC.Parser.Lexer hiding (context)
 import GHC.LanguageExtensions.Type
-import ApiAnnotation
-import DynFlags hiding (extensions)
-import Bag
+import GHC.Driver.Session hiding (extensions)
+import GHC.Data.Bag
+import Data.Generics.Uniplate.DataOnly
 
 import Language.Haskell.GhclibParserEx.GHC.Parser
 import Language.Haskell.GhclibParserEx.Fixity
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session
+
 import GHC.Util
 
 -- | What C pre processor should be used.
 data CppFlags
-    = NoCpp -- ^ No pre processing is done.
-    | CppSimple -- ^ Lines prefixed with @#@ are stripped.
+    = CppSimple -- ^ Lines prefixed with @#@ are stripped.
     | Cpphs CpphsOptions -- ^ The @cpphs@ library is used.
 
 -- | Created with 'defaultParseFlags', used by 'parseModuleEx'.
@@ -53,7 +60,7 @@
 
 -- | Default value for 'ParseFlags'.
 defaultParseFlags :: ParseFlags
-defaultParseFlags = ParseFlags NoCpp Nothing defaultExtensions [] defaultFixities
+defaultParseFlags = ParseFlags CppSimple Nothing defaultExtensions [] defaultFixities
 
 -- | Given some fixities, add them to the existing fixities in 'ParseFlags'.
 parseFlagsAddFixities :: [FixityInfo] -> ParseFlags -> ParseFlags
@@ -64,7 +71,6 @@
 
 
 runCpp :: CppFlags -> FilePath -> String -> IO String
-runCpp NoCpp _ x = pure x
 runCpp CppSimple _ x = pure $ unlines [if "#" `isPrefixOf` trimStart x then "" else x | x <- lines x]
 runCpp (Cpphs o) file x = dropLine <$> runCpphs o file x
     where
@@ -83,27 +89,36 @@
     }
 
 -- | Result of 'parseModuleEx', representing a parsed module.
-data ModuleEx = ModuleEx {
+newtype ModuleEx = ModuleEx {
     ghcModule :: Located (HsModule GhcPs)
-  , ghcAnnotations :: ApiAnns
 }
 
--- | Extract a list of all of a parsed module's comments.
-ghcComments :: ModuleEx -> [Located AnnotationComment]
-ghcComments m = concat (Map.elems $ snd (ghcAnnotations m))
+-- | Extract a complete list of all the comments in a module.
+ghcComments :: ModuleEx -> [LEpaComment]
+ghcComments = universeBi . ghcModule
 
+-- | Extract just the list of a modules' leading comments (pragmas).
+modComments :: ModuleEx -> EpAnnComments
+modComments = comments . hsmodAnn . hsmodExt . unLoc . ghcModule
 
+-- | Extract comments associated with the first declaration of a module.
+firstDeclComments :: ModuleEx -> EpAnnComments
+firstDeclComments m =
+  case hsmodDecls . unLoc . ghcModule $ m of
+        [] -> EpaCommentsBalanced [] []
+        L ann _ : _ -> comments ann
+
 -- | The error handler invoked when GHC parsing has failed.
 ghcFailOpParseModuleEx :: String
                        -> FilePath
                        -> String
-                       -> (SrcSpan, ErrUtils.MsgDoc)
+                       -> (SrcSpan, SDoc)
                        -> IO (Either ParseError ModuleEx)
 ghcFailOpParseModuleEx ppstr file str (loc, err) = do
    let pe = case loc of
-            RealSrcSpan r -> context (srcSpanStartLine r) ppstr
+            RealSrcSpan r _ -> context (srcSpanStartLine r) ppstr
             _ -> ""
-       msg = Outputable.showSDoc baseDynFlags err
+       msg = GHC.Driver.Ppr.showSDoc baseDynFlags err
    pure $ Left $ ParseError loc msg pe
 
 -- GHC extensions to enable/disable given HSE parse flags.
@@ -111,15 +126,15 @@
 ghcExtensionsFromParseFlags ParseFlags{enabledExtensions=es, disabledExtensions=ds}= (es, ds)
 
 -- GHC fixities given HSE parse flags.
-ghcFixitiesFromParseFlags :: ParseFlags -> [(String, Fixity)]
+ghcFixitiesFromParseFlags :: ParseFlags -> [(String, GHC.Types.Fixity.Fixity)]
 ghcFixitiesFromParseFlags = map toFixity . fixities
 
--- These next two functions get called frorm 'Config/Yaml.hs' for user
+-- These next two functions get called from 'Config/Yaml.hs' for user
 -- defined hint rules.
 
 parseModeToFlags :: ParseFlags -> DynFlags
 parseModeToFlags parseMode =
-    flip lang_set (baseLanguage parseMode) $ foldl' xopt_unset (foldl' xopt_set baseDynFlags enable) disable
+  flip lang_set (baseLanguage parseMode) $ foldl xopt_unset (foldl' xopt_set baseDynFlags enable) disable
   where
     (enable, disable) = ghcExtensionsFromParseFlags parseMode
 
@@ -141,13 +156,26 @@
     POk pst a -> POk pst $ applyFixities fixities a
     f@PFailed{} -> f
 
--- | Create a 'ModuleEx' from GHC annotations and module tree. It
--- is assumed the incoming parse module has not been adjusted to
--- account for operator fixities (it uses the HLint default fixities).
-createModuleEx :: ApiAnns -> Located (HsModule GhcPs) -> ModuleEx
-createModuleEx anns ast =
-  ModuleEx (applyFixities (fixitiesFromModule ast ++ map toFixity defaultFixities) ast) anns
+-- | Create a 'ModuleEx' from a GHC module. It is assumed the incoming
+-- parsed module has not been adjusted to account for operator
+-- fixities (it uses the HLint default fixities).
+createModuleEx :: Located (HsModule GhcPs) -> ModuleEx
+createModuleEx = createModuleExWithFixities (map toFixity defaultFixities)
 
+createModuleExWithFixities :: [(String, Fixity)] -> Located (HsModule GhcPs) -> ModuleEx
+createModuleExWithFixities fixities ast =
+  ModuleEx (applyFixities (fixitiesFromModule ast ++ fixities) ast)
+
+impliedEnables :: Extension -> [Extension]
+impliedEnables ext = case Data.List.lookup ext extensionImplications of
+  Just exts -> ext : fst exts
+  Nothing -> [ext]
+
+impliedDisables :: Extension -> [Extension]
+impliedDisables ext = case Data.List.lookup ext extensionImplications  of
+  Just exts -> ext : snd exts
+  Nothing -> []
+
 -- | Parse a Haskell module. Applies the C pre processor, and uses
 -- best-guess fixity resolution if there are ambiguities.  The
 -- filename @-@ is treated as @stdin@. Requires some flags (often
@@ -164,7 +192,11 @@
     Nothing | file == "-" -> liftIO getContentsUTF8
             | otherwise -> liftIO $ readFileUTF8' file
   str <- pure $ dropPrefix "\65279" str -- Remove the BOM if it exists, see #130.
-  let enableDisableExts = ghcExtensionsFromParseFlags flags
+  let (enable, disable) = ghcExtensionsFromParseFlags flags
+  -- Enable/disable extensions and the extensions they imply.
+      impliedEnabled = concatMap impliedEnables enable
+      impliedDisabled = concatMap impliedDisables disable
+      enableDisableExts = (impliedEnabled, impliedDisabled)
   -- Read pragmas for the first time.
   dynFlags <- withExceptT (parsePragmasErr str) $ ExceptT (parsePragmasIntoDynFlags baseDynFlags enableDisableExts file str)
   dynFlags <- pure $ lang_set dynFlags $ baseLanguage flags
@@ -177,18 +209,14 @@
   -- Done with pragmas. Proceed to parsing.
   case fileToModule file str dynFlags of
     POk s a -> do
-      let errs = bagToList . snd $ getMessages s dynFlags
+      let errs = bagToList . getMessages $ GhcPsMessage <$> snd (getPsMessages s)
       if not $ null errs then
-        ExceptT $ parseFailureErr dynFlags str file str errs
+        ExceptT $ parseFailureErr dynFlags str file str $ NE.fromList errs
       else do
-        let anns =
-              ( Map.fromListWith (++) $ annotations s
-              , Map.fromList ((noSrcSpan, comment_q s) : annotations_comments s)
-              )
         let fixes = fixitiesFromModule a ++ ghcFixitiesFromParseFlags flags
-        pure $ ModuleEx (applyFixities fixes a) anns
+        pure $ ModuleEx (applyFixities fixes a)
     PFailed s ->
-      ExceptT $ parseFailureErr dynFlags str file str $  bagToList . snd $ getMessages s dynFlags
+      ExceptT $ parseFailureErr dynFlags str file str $ NE.fromList . bagToList . getMessages  $ GhcPsMessage <$> snd (getPsMessages s)
   where
     -- If parsing pragmas fails, synthesize a parse error from the
     -- error message.
@@ -197,9 +225,9 @@
       in ParseError (mkSrcSpan loc loc) msg src
 
     parseFailureErr dynFlags ppstr file str errs =
-      let errMsg = head errs
+      let errMsg = NE.head errs
           loc = errMsgSpan errMsg
-          doc = formatErrDoc dynFlags (errMsgDoc errMsg)
+          doc = pprLocMsgEnvelopeDefault errMsg
       in ghcFailOpParseModuleEx ppstr file str (loc, doc)
 
 -- | Given a line number, and some source code, put bird ticks around the appropriate bit.
diff --git a/src/GHC/Util.hs b/src/GHC/Util.hs
--- a/src/GHC/Util.hs
+++ b/src/GHC/Util.hs
@@ -33,10 +33,10 @@
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 import GHC.Hs
-import Lexer
-import SrcLoc
-import DynFlags
-import FastString
+import GHC.Parser.Lexer
+import GHC.Types.SrcLoc
+import GHC.Driver.Session
+import GHC.Data.FastString
 
 import System.FilePath
 import Language.Preprocessor.Unlit
@@ -66,7 +66,7 @@
       ))
 
 toOldeSpan :: SrcSpan -> (String, Int, Int, Int, Int)
-toOldeSpan (RealSrcSpan span) =
+toOldeSpan (RealSrcSpan span _) =
   ( unpackFS $ srcSpanFile span
   , srcSpanStartLine span
   , srcSpanStartCol span
@@ -75,8 +75,8 @@
   )
 -- TODO: the bad locations are all (-1) right now
 -- is this fine? it should be, since noLoc from HSE previously also used (-1) as an invalid location
-toOldeSpan (UnhelpfulSpan str) =
-  ( unpackFS str
+toOldeSpan (UnhelpfulSpan _) =
+  ( "no-span"
   , -1
   , -1
   , -1
@@ -98,13 +98,13 @@
       ))
 
 toOldeLoc :: SrcLoc -> (String, Int, Int)
-toOldeLoc (RealSrcLoc loc) =
+toOldeLoc (RealSrcLoc loc _) =
   ( unpackFS $ srcLocFile loc
   , srcLocLine loc
   , srcLocCol loc
   )
-toOldeLoc (UnhelpfulLoc str) =
-  ( unpackFS str
+toOldeLoc (UnhelpfulLoc _) =
+  ( "no-loc"
   , -1
   , -1
   )
diff --git a/src/GHC/Util/ApiAnnotation.hs b/src/GHC/Util/ApiAnnotation.hs
--- a/src/GHC/Util/ApiAnnotation.hs
+++ b/src/GHC/Util/ApiAnnotation.hs
@@ -1,17 +1,29 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 
 module GHC.Util.ApiAnnotation (
-    comment, commentText, isCommentMultiline
-  , pragmas, flags, languagePragmas
-  , mkFlags, mkLanguagePragmas
+    comment_
+  , commentText
+  , GHC.Util.ApiAnnotation.comments
+  , isCommentMultiline
+  , pragmas
+  , flags
+  , languagePragmas
+  , mkFlags
+  , mkLanguagePragmas
+  , extensions
 ) where
 
-import ApiAnnotation
-import SrcLoc
+import GHC.LanguageExtensions.Type (Extension)
+import GHC.Parser.Annotation
+import GHC.Hs.DocString
+import GHC.Types.SrcLoc
 
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session
+
 import Control.Applicative
-import qualified Data.Map.Strict as Map
-import Data.Maybe
 import Data.List.Extra
+import Data.Maybe
+import Data.Set qualified as Set
 
 trimCommentStart :: String -> String
 trimCommentStart s
@@ -28,41 +40,43 @@
 trimCommentDelims = trimCommentEnd . trimCommentStart
 
 -- | A comment as a string.
-comment :: Located AnnotationComment -> String
-comment (L _ (AnnBlockComment s)) = s
-comment (L _ (AnnLineComment s)) = s
-comment (L _ (AnnDocOptions s)) = s
-comment (L _ (AnnDocCommentNamed s)) = s
-comment (L _ (AnnDocCommentPrev s)) = s
-comment (L _ (AnnDocCommentNext s)) = s
-comment (L _ (AnnDocSection _ s)) = s
+comment_ :: LEpaComment -> String
+comment_ (L _ (EpaComment (EpaDocComment ds ) _)) = renderHsDocString ds
+comment_ (L _ (EpaComment (EpaDocOptions s) _)) = s
+comment_ (L _ (EpaComment (EpaLineComment s) _)) = s
+comment_ (L _ (EpaComment (EpaBlockComment s) _)) = s
 
 -- | The comment string with delimiters removed.
-commentText :: Located AnnotationComment -> String
-commentText = trimCommentDelims . comment
+commentText :: LEpaComment -> String
+commentText = trimCommentDelims . comment_
 
-isCommentMultiline :: Located AnnotationComment -> Bool
-isCommentMultiline (L _ (AnnBlockComment _)) = True
-isCommentMultiline _ = False
+-- | Total replacement for the partial `GHC.Parser.Annotation.comments` field of
+-- `EpAnn`
+comments :: EpAnn ann -> EpAnnComments
+comments EpAnn{ GHC.Parser.Annotation.comments = result } = result
 
--- GHC parse trees don't contain pragmas. We work around this with
--- (nasty) parsing of comments.
+isCommentMultiline :: LEpaComment -> Bool
+isCommentMultiline (L _ (EpaComment (EpaBlockComment _) _)) = True
+isCommentMultiline _ = False
 
--- Pragmas. Comments not associated with a span in the annotations
--- that have the form @{-# ...#-}@.
-pragmas :: ApiAnns -> [(Located AnnotationComment, String)]
-pragmas anns =
-  -- 'ApiAnns' stores pragmas in reverse order to how they were
+-- Pragmas have the form @{-# ...#-}@.
+pragmas :: EpAnnComments -> [(LEpaComment, String)]
+pragmas x =
+  -- 'EpaAnnComments' stores pragmas in reverse order to how they were
   -- encountered in the source file with the last at the head of the
   -- list (makes sense when you think about it).
   reverse
     [ (c, s) |
-        c@(L _ (AnnBlockComment comm)) <- fromMaybe [] $ Map.lookup noSrcSpan (snd anns)
+        c@(L _ (EpaComment (EpaBlockComment comm) _)) <- priorComments x
       , let body = trimCommentDelims comm
       , Just rest <- [stripSuffix "#" =<< stripPrefix "#" body]
       , let s = trim rest
     ]
 
+-- All the extensions defined to be used.
+extensions :: EpAnnComments -> Set.Set Extension
+extensions = Set.fromList . concatMap (mapMaybe readExtension . snd) . languagePragmas . pragmas
+
 -- Utility for a case insensitive prefix strip.
 stripPrefixCI :: String -> String -> Maybe String
 stripPrefixCI pref str =
@@ -70,11 +84,9 @@
       (str_pref, rest) = splitAt (length pref') str
   in if lower str_pref == pref' then Just rest else Nothing
 
--- Flags. The first element of the pair is the (located) annotation
--- comment that sets the flags enumerated in the second element of the
--- pair.
-flags :: [(Located AnnotationComment, String)]
-      -> [(Located AnnotationComment, [String])]
+-- Flags. The first element of the pair is the comment that
+-- sets the flags enumerated in the second element of the pair.
+flags :: [(LEpaComment, String)] -> [(LEpaComment, [String])]
 flags ps =
   -- Old versions of GHC accepted 'OPTIONS' rather than 'OPTIONS_GHC' (but
   -- this is deprecated).
@@ -85,19 +97,18 @@
 
 -- Language pragmas. The first element of the
 -- pair is the (located) annotation comment that enables the
--- pragmas enumerated by he second element of the pair.
-languagePragmas :: [(Located AnnotationComment, String)]
-         -> [(Located AnnotationComment, [String])]
+-- pragmas enumerated by the second element of the pair.
+languagePragmas :: [(LEpaComment, String)] -> [(LEpaComment, [String])]
 languagePragmas ps =
   [(c, exts) | (c, s) <- ps
              , Just rest <- [stripPrefixCI "LANGUAGE " s]
              , let exts = map trim (splitOn "," rest)]
 
 -- Given a list of flags, make a GHC options pragma.
-mkFlags :: SrcSpan -> [String] -> Located AnnotationComment
-mkFlags loc flags =
-  L loc $ AnnBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}")
+mkFlags :: NoCommentsLocation -> [String] -> LEpaComment
+mkFlags anc flags =
+  L anc $ EpaComment (EpaBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}")) (epaLocationRealSrcSpan anc)
 
-mkLanguagePragmas :: SrcSpan -> [String] -> Located AnnotationComment
-mkLanguagePragmas loc exts =
-  L loc $ AnnBlockComment ("{-# " ++ "LANGUAGE " ++ intercalate ", " exts ++ " #-}")
+mkLanguagePragmas :: NoCommentsLocation -> [String] -> LEpaComment
+mkLanguagePragmas anc exts =
+  L anc $ EpaComment (EpaBlockComment ("{-# " ++ "LANGUAGE " ++ intercalate ", " exts ++ " #-}")) (epaLocationRealSrcSpan anc)
diff --git a/src/GHC/Util/Brackets.hs b/src/GHC/Util/Brackets.hs
--- a/src/GHC/Util/Brackets.hs
+++ b/src/GHC/Util/Brackets.hs
@@ -4,8 +4,8 @@
 module GHC.Util.Brackets (Brackets(..), isApp,isOpApp,isAnyApp) where
 
 import GHC.Hs
-import SrcLoc
-import BasicTypes
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
 import Refact.Types
 
@@ -20,7 +20,7 @@
   needBracket :: Int -> a -> a -> Bool
   findType :: a -> RType
 
-instance Brackets (LHsExpr GhcPs) where
+instance Brackets (LocatedA (HsExpr GhcPs)) where
   -- When GHC parses a section in concrete syntax, it will produce an
   -- 'HsPar (Section[L|R])'. There is no concrete syntax that will
   -- result in a "naked" section. Consequently, given an expression,
@@ -31,12 +31,13 @@
   remParen (L _ (HsPar _ x)) = Just x
   remParen _ = Nothing
 
-  addParen e = noLoc $ HsPar noExtField e
+  addParen = nlHsPar
 
   isAtom (L _ x) = case x of
       HsVar{} -> True
       HsUnboundVar{} -> True
-      HsRecFld{} -> True
+      -- Only relevant for OverloadedRecordDot extension
+      HsGetField{} -> True
       HsOverLabel{} -> True
       HsIPVar{} -> True
       -- Note that sections aren't atoms (but parenthesized sections are).
@@ -47,10 +48,12 @@
       RecordCon{} -> True
       RecordUpd{} -> True
       ArithSeq{}-> True
-      HsBracket{} -> True
+      HsTypedBracket{} -> True
+      HsUntypedBracket{} -> True
       -- HsSplice might be $foo, where @($foo) would require brackets,
       -- but in that case the $foo is a type, so we can still mark Splice as atomic
-      HsSpliceE{} -> True
+      HsTypedSplice{} -> True
+      HsUntypedSplice{} -> True
       HsOverLit _ x | not $ isNegativeOverLit x -> True
       HsLit _ x     | not $ isNegativeLit x     -> True
       _  -> False
@@ -97,22 +100,26 @@
 --
 --   (f \x -> x) *> ...
 --   (f do x) *> ...
-isAtomOrApp :: LHsExpr GhcPs -> Bool
+isAtomOrApp :: LocatedA (HsExpr GhcPs) -> Bool
 isAtomOrApp x | isAtom x = True
 isAtomOrApp (L _ (HsApp _ _ x)) = isAtomOrApp x
 isAtomOrApp _ = False
 
-instance Brackets (Located (Pat GhcPs)) where
+instance Brackets (LocatedA (Pat GhcPs)) where
   remParen (L _ (ParPat _ x)) = Just x
   remParen _ = Nothing
-  addParen e = noLoc $ ParPat noExtField e
 
+  addParen = nlParPat
+
   isAtom (L _ x) = case x of
     ParPat{} -> True
     TuplePat{} -> True
     ListPat{} -> True
-    ConPatIn _ RecCon{} -> True
-    ConPatIn _ (PrefixCon []) -> True
+    -- This is technically atomic, but lots of people think it shouldn't be
+    ConPat _ _ RecCon{} -> False
+    -- Before we only checked args, but not type args, resulting in a
+    -- false positive for things like (Proxy @a)
+    ConPat _ _ (PrefixCon [] []) -> True
     VarPat{} -> True
     WildPat{} -> True
     SumPat{} -> True
@@ -139,10 +146,10 @@
 
   findType _ = Pattern
 
-instance Brackets (LHsType GhcPs) where
+instance Brackets (LocatedA (HsType GhcPs)) where
   remParen (L _ (HsParTy _ x)) = Just x
   remParen _ = Nothing
-  addParen e = noLoc $ HsParTy noExtField e
+  addParen e = noLocA $ HsParTy noAnn e
 
   isAtom (L _ x) = case x of
       HsParTy{} -> True
diff --git a/src/GHC/Util/DynFlags.hs b/src/GHC/Util/DynFlags.hs
--- a/src/GHC/Util/DynFlags.hs
+++ b/src/GHC/Util/DynFlags.hs
@@ -1,23 +1,14 @@
 module GHC.Util.DynFlags (initGlobalDynFlags, baseDynFlags) where
 
-import DynFlags
-import GHC.LanguageExtensions.Type
-import Data.List.Extra
+import GHC.Driver.Session
 import Language.Haskell.GhclibParserEx.GHC.Settings.Config
 
+-- The list of default enabled extensions is empty. This is because:
+-- the extensions to enable/disable are set exclusively in
+-- 'parsePragmasIntoDynFlags' based solely on the parse flags
+-- (and source level annotations).
 baseDynFlags :: DynFlags
-baseDynFlags =
-  -- The list of default enabled extensions is empty except for
-  -- 'TemplateHaskellQuotes'. This is because:
-  --  * The extensions to enable/disable are set exclusively in
-  --    'parsePragmasIntoDynFlags' based solely on HSE parse flags
-  --    (and source level annotations);
-  --  * 'TemplateHaskellQuotes' is not a known HSE extension but IS
-  --    needed if the GHC parse is to succeed for the unit-test at
-  --    hlint.yaml:860
-  let enable = [TemplateHaskellQuotes]
-  in foldl' xopt_set (defaultDynFlags fakeSettings fakeLlvmConfig) enable
-
+baseDynFlags = defaultDynFlags fakeSettings
 
 initGlobalDynFlags :: IO ()
 initGlobalDynFlags = setUnsafeGlobalDynFlags baseDynFlags
diff --git a/src/GHC/Util/FreeVars.hs b/src/GHC/Util/FreeVars.hs
--- a/src/GHC/Util/FreeVars.hs
+++ b/src/GHC/Util/FreeVars.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE TypeFamilies #-}
@@ -7,20 +8,18 @@
     Vars (..), FreeVars(..) , AllVars (..)
   ) where
 
-import RdrName
-import GHC.Hs.Types
-import OccName
-import Name
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name
 import GHC.Hs
-import SrcLoc
-import Bag (bagToList)
+import GHC.Types.SrcLoc
 
 import Data.Generics.Uniplate.DataOnly
 import Data.Monoid
 import Data.Semigroup
 import Data.List.Extra
 import Data.Set (Set)
-import qualified Data.Set as Set
+import Data.Set qualified as Set
 import Prelude
 
 ( ^+ ) :: Set OccName -> Set OccName -> Set OccName
@@ -45,7 +44,7 @@
     mconcat vs = Vars (Set.unions $ map bound vs) (Set.unions $ map free vs)
 
 -- A type `a` is a model of `AllVars a` if exists a function
--- `allVars` for producing a pair of the bound and free varaiable
+-- `allVars` for producing a pair of the bound and free variable
 -- sets in a value of `a`.
 class AllVars a where
     -- | Return the variables, erring on the side of more free
@@ -53,7 +52,7 @@
     allVars :: a -> Vars
 
 -- A type `a` is a model of `FreeVars a` if exists a function
--- `freeVars` for producing a set of free varaiable of a value of
+-- `freeVars` for producing a set of free variables of a value of
 -- `a`.
 class FreeVars a where
     -- | Return the variables, erring on the side of more free
@@ -91,32 +90,48 @@
           bb = allVars b
 
 -- Get an `OccName` out of a reader name.
-unqualNames :: Located RdrName -> [OccName]
+unqualNames :: LocatedN RdrName -> [OccName]
 unqualNames (L _ (Unqual x)) = [x]
 unqualNames (L _ (Exact x)) = [nameOccName x]
 unqualNames _ = []
 
-instance FreeVars (LHsExpr GhcPs) where
+instance FreeVars (LocatedA (HsExpr GhcPs)) where
   freeVars (L _ (HsVar _ x)) = Set.fromList $ unqualNames x -- Variable.
-  freeVars (L _ (HsUnboundVar _ x)) = Set.fromList [unboundVarOcc x] -- Unbound variable; also used for "holes".
-  freeVars (L _ (HsLam _ mg)) = free (allVars mg) -- Lambda abstraction. Currently always a single match.
-  freeVars (L _ (HsLamCase _ MG{mg_alts=(L _ ms)})) = free (allVars ms) -- Lambda case
+  freeVars (L _ (HsUnboundVar _ x)) = Set.fromList [rdrNameOcc x] -- Unbound variable; also used for "holes".
+  freeVars (L _ (HsLam _ LamSingle mg)) = free (allVars mg) -- Lambda abstraction. Currently always a single match.
+  freeVars (L _ (HsLam _ _ MG{mg_alts=(L _ ms)})) = free (allVars ms) -- Lambda case
   freeVars (L _ (HsCase _ of_ MG{mg_alts=(L _ ms)})) = freeVars of_ ^+ free (allVars ms) -- Case expr.
   freeVars (L _ (HsLet _ binds e)) = inFree binds e -- Let (rec).
-  freeVars (L _ (HsDo _ ctxt (L _ stmts))) = free (allVars stmts) -- Do block.
-  freeVars (L _ (RecordCon _ _ (HsRecFields flds _))) = Set.unions $ map freeVars flds -- Record construction.
-  freeVars (L _ (RecordUpd _ e flds)) = Set.unions $ freeVars e : map freeVars flds -- Record update.
+  freeVars (L _ (HsDo _ ctxt (L _ stmts))) = snd $ foldl' alg mempty stmts -- Do block.
+    where
+      alg ::
+        (Set OccName, Set OccName) ->
+        LocatedA (StmtLR GhcPs GhcPs (LocatedA (HsExpr GhcPs))) ->
+        (Set OccName, Set OccName)
+      alg (accBound0, accFree0) stmt = (accBound, accFree)
+        where
+          accBound =
+            accBound0
+              ^+ ( case stmt of
+                     L _ (BindStmt _ pat _) -> bound (allVars pat)
+                     L _ (LetStmt _ binds) -> bound (allVars binds)
+                     _ -> mempty
+                 )
+          accFree = accFree0 ^+ (free (allVars stmt) ^- accBound0)
+  freeVars (L _ (RecordCon _ _ (HsRecFields _ flds _))) = Set.unions $ map freeVars flds -- Record construction.
+  freeVars (L _ (RecordUpd _ e flds)) =
+    case flds of
+      RegularRecUpdFields _ fs -> Set.unions $ freeVars e : map freeVars fs
+      OverloadedRecUpdFields _ ps -> Set.unions $ freeVars e : map freeVars ps
   freeVars (L _ (HsMultiIf _ grhss)) = free (allVars grhss) -- Multi-way if.
+  freeVars (L _ (HsTypedBracket _ e)) = freeVars e
+  freeVars (L _ (HsUntypedBracket _ (ExpBr _ e))) = freeVars e
+  freeVars (L _ (HsUntypedBracket _ (VarBr _ _ v))) = Set.fromList [occName (unLoc v)]
 
-  freeVars (L _ HsConLikeOut{}) = mempty -- After typechecker.
-  freeVars (L _ HsRecFld{}) = mempty -- Variable pointing to a record selector.
   freeVars (L _ HsOverLabel{}) = mempty -- Overloaded label. The id of the in-scope fromLabel.
   freeVars (L _ HsIPVar{}) = mempty -- Implicit parameter.
   freeVars (L _ HsOverLit{}) = mempty -- Overloaded literal.
   freeVars (L _ HsLit{}) = mempty -- Simple literal.
-  freeVars (L _ HsRnBracketOut{}) = mempty -- Renamer produces these.
-  freeVars (L _ HsTcBracketOut{}) = mempty -- Typechecker produces these.
-  freeVars (L _ HsWrap{}) = mempty -- Typechecker output.
 
   -- freeVars (e@(L _ HsAppType{})) = freeVars $ children e -- Visible type application e.g. f @ Int x y.
   -- freeVars (e@(L _ HsApp{})) = freeVars $ children e -- Application.
@@ -148,28 +163,27 @@
 
   freeVars e = freeVars $ children e
 
-instance FreeVars (LHsTupArg GhcPs) where
-  freeVars (L _ (Present _ args)) = freeVars args
+instance FreeVars (HsTupArg GhcPs) where
+  freeVars (Present _ args) = freeVars args
   freeVars _ = mempty
 
-instance FreeVars (LHsRecField GhcPs (LHsExpr GhcPs)) where
-   freeVars o@(L _ (HsRecField x _ True)) = Set.singleton $ occName $ unLoc $ rdrNameFieldOcc $ unLoc x -- a pun
-   freeVars o@(L _ (HsRecField _ x _)) = freeVars x
+instance FreeVars (LocatedA (HsFieldBind (LocatedA (FieldOcc GhcPs)) (LocatedA (HsExpr GhcPs)))) where
+   freeVars o@(L _ (HsFieldBind _ x _ True)) = Set.singleton $ occName $ unLoc $ foLabel $ unLoc x -- a pun
+   freeVars o@(L _ (HsFieldBind _ _ x _)) = freeVars x
 
-instance FreeVars (LHsRecUpdField GhcPs) where
-  freeVars (L _ (HsRecField _ x _)) = freeVars x
+instance FreeVars (LocatedA (HsFieldBind (LocatedAn NoEpAnns (FieldLabelStrings GhcPs)) (LocatedA (HsExpr GhcPs)))) where
+  freeVars (L _ (HsFieldBind _ _ x _)) = freeVars x
 
-instance AllVars (Located (Pat GhcPs)) where
+instance AllVars (LocatedA (Pat GhcPs)) where
   allVars (L _ (VarPat _ (L _ x))) = Vars (Set.singleton $ rdrNameOcc x) Set.empty -- Variable pattern.
-  allVars (L _ (AsPat _  n x)) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) <> allVars x -- As pattern.
-  allVars (L _ (ConPatIn _ (RecCon (HsRecFields flds _)))) = allVars flds
-  allVars (L _ (NPlusKPat _ n _ _ _ _)) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) -- n+k pattern.
+  allVars (L _ (AsPat _ n x)) = allVars (noLocA $ VarPat noExtField n :: LocatedA (Pat GhcPs)) <> allVars x -- As pattern.
+  allVars (L _ (ConPat _ _ (RecCon (HsRecFields _ flds _)))) = allVars flds
+  allVars (L _ (NPlusKPat _ n _ _ _ _)) = allVars (noLocA $ VarPat noExtField n :: LocatedA (Pat GhcPs)) -- n+k pattern.
   allVars (L _ (ViewPat _ e p)) = freeVars_ e <> allVars p -- View pattern.
-
   allVars (L _ WildPat{}) = mempty -- Wildcard pattern.
-  allVars (L _ ConPatOut{}) = mempty -- Renamer/typechecker.
   allVars (L _ LitPat{}) = mempty -- Literal pattern.
   allVars (L _ NPat{}) = mempty -- Natural pattern.
+  allVars (L _ InvisPat {}) = mempty -- since ghc-9.10.1
 
   -- allVars p@SplicePat{} = allVars $ children p -- Splice pattern (includes quasi-quotes).
   -- allVars p@SigPat{} = allVars $ children p -- Pattern with a type signature.
@@ -183,78 +197,57 @@
 
   allVars p = allVars $ children p
 
-instance AllVars (LHsRecField GhcPs (Located (Pat GhcPs))) where
-   allVars (L _ (HsRecField _ x _)) = allVars x
+instance AllVars (LocatedA (HsFieldBind (LocatedA (FieldOcc GhcPs)) (LocatedA (Pat GhcPs)))) where
+   allVars (L _ (HsFieldBind _ _ x _)) = allVars x
 
-instance AllVars (LStmt GhcPs (LHsExpr GhcPs)) where
+instance AllVars (LocatedA (StmtLR GhcPs GhcPs (LocatedA (HsExpr GhcPs)))) where
   allVars (L _ (LastStmt _ expr _ _)) = freeVars_ expr -- The last stmt of a ListComp, MonadComp, DoExpr,MDoExpr.
-  allVars (L _ (BindStmt _ pat expr _ _)) = allVars pat <> freeVars_ expr -- A generator e.g. x <- [1, 2, 3].
+  allVars (L _ (BindStmt _ pat expr)) = allVars pat <> freeVars_ expr -- A generator e.g. x <- [1, 2, 3].
   allVars (L _ (BodyStmt _ expr _ _)) = freeVars_ expr -- A boolean guard e.g. even x.
   allVars (L _ (LetStmt _ binds)) = allVars binds -- A local declaration e.g. let y = x + 1
-  allVars (L _ (TransStmt _ _ stmts _ using by _ _ fmap_)) = allVars stmts <> freeVars_ using <> maybe mempty freeVars_ by <> freeVars_ (noLoc fmap_ :: Located (HsExpr GhcPs)) -- Apply a function to a list of statements in order.
-  allVars (L _ (RecStmt _ stmts _ _ _ _ _)) = allVars stmts -- A recursive binding for a group of arrows.
-
-  allVars (L _ ApplicativeStmt{}) = mempty -- Generated by the renamer.
+  allVars (L _ (TransStmt _ _ stmts _ using by _ _ fmap_)) = allVars stmts <> freeVars_ using <> maybe mempty freeVars_ by <> freeVars_ (noLocA fmap_ :: LocatedA (HsExpr GhcPs)) -- Apply a function to a list of statements in order.
+  allVars (L _ (RecStmt _ stmts _ _ _ _ _)) = allVars (unLoc stmts) -- A recursive binding for a group of arrows.
   allVars (L _ ParStmt{}) = mempty -- Parallel list thing. Come back to it.
 
-  allVars _ = mempty -- New ctor.
-
-instance AllVars (LHsLocalBinds GhcPs) where
-  allVars (L _ (HsValBinds _ (ValBinds _ binds _))) = allVars (bagToList binds) -- Value bindings.
-  allVars (L _ (HsIPBinds _ (IPBinds _ binds))) = allVars binds -- Implicit parameter bindings.
-
-  allVars (L _ EmptyLocalBinds{}) =  mempty -- The case of no local bindings (signals the empty `let` or `where` clause).
-
-  allVars _ = mempty -- New ctor.
+instance AllVars (HsLocalBinds GhcPs) where
+  allVars (HsValBinds _ (ValBinds _ binds _)) = allVars binds -- Value bindings.
+  allVars (HsIPBinds _ (IPBinds _ binds)) = allVars binds -- Implicit parameter bindings.
+  allVars EmptyLocalBinds{} =  mempty -- The case of no local bindings (signals the empty `let` or `where` clause).
+  allVars _ = mempty -- extension points
 
-instance AllVars (LIPBind GhcPs) where
+instance AllVars (LocatedA (IPBind GhcPs)) where
   allVars (L _ (IPBind _ _ e)) = freeVars_ e
 
-  allVars _ = mempty -- New ctor.
-
-instance AllVars (LHsBind GhcPs) where
-  allVars (L _ FunBind{fun_id=n, fun_matches=MG{mg_alts=(L _ ms)}}) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) <> allVars ms -- Function bindings and simple variable bindings e.g. f x = e, f !x = 3, f = e, !x = e, x `f` y = e
+instance AllVars (LocatedA (HsBindLR GhcPs GhcPs)) where
+  allVars (L _ FunBind{fun_id=n, fun_matches=MG{mg_alts=(L _ ms)}}) = allVars (noLocA $ VarPat noExtField n :: LocatedA (Pat GhcPs)) <> allVars ms -- Function bindings and simple variable bindings e.g. f x = e, f !x = 3, f = e, !x = e, x `f` y = e
   allVars (L _ PatBind{pat_lhs=n, pat_rhs=grhss}) = allVars n <> allVars grhss -- Ctor patterns and some other interesting cases e.g. Just x = e, (x) = e, x :: Ty = e.
 
   allVars (L _ (PatSynBind _ PSB{})) = mempty -- Come back to it.
-  allVars (L _ VarBind{}) = mempty -- Typechecker.
-  allVars (L _ AbsBinds{}) = mempty -- Not sure but I think renamer.
 
-  allVars _ = mempty -- New ctor.
-
-instance AllVars (MatchGroup GhcPs (LHsExpr GhcPs)) where
-  allVars (MG _ _alts@(L _ alts) _) = inVars (foldMap (allVars . m_pats) ms) (allVars (map m_grhss ms))
+instance AllVars (MatchGroup GhcPs (LocatedA (HsExpr GhcPs))) where
+  allVars (MG _ _alts@(L _ alts)) = foldMap (\m -> inVars (allVars ((unLoc . m_pats) m)) (allVars (m_grhss m))) ms
     where ms = map unLoc alts
-  allVars _ = mempty -- New ctor.
 
-instance AllVars (LMatch GhcPs (LHsExpr GhcPs)) where
-  allVars (L _ (Match _ FunRhs {mc_fun=name} pats grhss)) = allVars (noLoc $ VarPat noExtField name :: LPat GhcPs) <> allVars pats <> allVars grhss -- A pattern matching on an argument of a function binding.
-  allVars (L _ (Match _ (StmtCtxt ctxt) pats grhss)) = allVars ctxt <> allVars pats <> allVars grhss -- Pattern of a do-stmt, list comprehension, pattern guard etc.
-  allVars (L _ (Match _ _ pats grhss)) = inVars (allVars pats) (allVars grhss) -- Everything else.
-
-  allVars _ = mempty -- New ctor.
+instance AllVars (LocatedA (Match GhcPs (LocatedA (HsExpr GhcPs)))) where
+  allVars (L _ (Match _ FunRhs {mc_fun=name} pats grhss)) = allVars (noLocA $ VarPat noExtField name :: LocatedA (Pat GhcPs)) <> (allVars . unLoc) pats <> allVars grhss -- A pattern matching on an argument of a function binding.
+  allVars (L _ (Match _ (StmtCtxt ctxt) pats grhss)) = allVars ctxt <> (allVars . unLoc) pats <> allVars grhss -- Pattern of a do-stmt, list comprehension, pattern guard etc.
+  allVars (L _ (Match _ _ pats grhss)) = inVars ((allVars . unLoc) pats) (allVars grhss) -- Everything else.
 
-instance AllVars (HsStmtContext RdrName) where
-  allVars (PatGuard FunRhs{mc_fun=n}) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs)
+instance AllVars (HsStmtContext (GenLocated SrcSpanAnnN RdrName)) where
+  allVars (PatGuard FunRhs{mc_fun=n}) = allVars (noLocA $ VarPat noExtField n :: LocatedA (Pat GhcPs))
   allVars ParStmtCtxt{} = mempty -- Come back to it.
   allVars TransStmtCtxt{}  = mempty -- Come back to it.
-
-  allVars _ = mempty -- Everything else (correct).
+  allVars _ = mempty
 
-instance AllVars (GRHSs GhcPs (LHsExpr GhcPs)) where
+instance AllVars (GRHSs GhcPs (LocatedA (HsExpr GhcPs))) where
   allVars (GRHSs _ grhss binds) = inVars binds (mconcatMap allVars grhss)
 
-  allVars _ = mempty -- New ctor.
-
-instance AllVars (LGRHS GhcPs (LHsExpr GhcPs)) where
+instance AllVars (LocatedAn NoEpAnns (GRHS GhcPs (LocatedA (HsExpr GhcPs)))) where
   allVars (L _ (GRHS _ guards expr)) = Vars (bound gs) (free gs ^+ (freeVars expr ^- bound gs)) where gs = allVars guards
 
-  allVars _ = mempty -- New ctor.
-
-instance AllVars (LHsDecl GhcPs) where
-  allVars (L l (ValD _ bind)) = allVars (L l bind :: LHsBind GhcPs)
-
-  allVars _ = mempty  -- We only consider value bindings.
+instance AllVars (LocatedA (HsDecl GhcPs)) where
+  allVars (L l (ValD _ bind)) = allVars (L l bind :: LocatedA (HsBindLR GhcPs GhcPs))
+  allVars _ = mempty
 
 
 vars :: FreeVars a => a -> [String]
diff --git a/src/GHC/Util/HsDecl.hs b/src/GHC/Util/HsDecl.hs
--- a/src/GHC/Util/HsDecl.hs
+++ b/src/GHC/Util/HsDecl.hs
@@ -4,7 +4,7 @@
 where
 
 import GHC.Hs
-import SrcLoc
+import GHC.Types.SrcLoc
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
 -- | @declName x@ returns the \"new name\" that is created (for
diff --git a/src/GHC/Util/HsExpr.hs b/src/GHC/Util/HsExpr.hs
--- a/src/GHC/Util/HsExpr.hs
+++ b/src/GHC/Util/HsExpr.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
 {-# LANGUAGE LambdaCase #-}
@@ -15,19 +16,19 @@
 ) where
 
 import GHC.Hs
-import BasicTypes
-import SrcLoc
-import FastString
-import RdrName
-import OccName
-import Bag(bagToList)
+import GHC.Types.Basic
+import GHC.Types.SrcLoc
+import GHC.Data.FastString
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
 
 import GHC.Util.Brackets
 import GHC.Util.FreeVars
 import GHC.Util.View
 
-import Control.Applicative
+import Control.Monad.Trans.Class
 import Control.Monad.Trans.State
+import Control.Monad.Trans.Writer.CPS
 
 import Data.Data
 import Data.Generics.Uniplate.DataOnly
@@ -35,9 +36,9 @@
 import Data.Tuple.Extra
 import Data.Maybe
 
-import Refact (substVars, toSS)
+import Refact (substVars, toSSA)
 import Refact.Types hiding (SrcSpan, Match)
-import qualified Refact.Types as R (SrcSpan)
+import Refact.Types qualified as R (SrcSpan)
 
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
@@ -47,7 +48,7 @@
 
 -- | 'dotApp a b' makes 'a . b'.
 dotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
-dotApp x y = noLoc $ OpApp noExtField x (noLoc $ HsVar noExtField (noLoc $ mkVarUnqual (fsLit "."))) y
+dotApp x y = noLocA $ OpApp noExtField x (noLocA $ HsVar noExtField (noLocA $ mkVarUnqual (fsLit "."))) y
 
 dotApps :: [LHsExpr GhcPs] -> LHsExpr GhcPs
 dotApps [] = error "GHC.Util.HsExpr.dotApps', does not work on an empty list"
@@ -56,7 +57,7 @@
 
 -- | @lambda [p0, p1..pn] body@ makes @\p1 p1 .. pn -> body@
 lambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs
-lambda vs body = noLoc $ HsLam noExtField (MG noExtField (noLoc [noLoc $ Match noExtField LambdaExpr vs (GRHSs noExtField [noLoc $ GRHS noExtField [] body] (noLoc $ EmptyLocalBinds noExtField))]) Generated)
+lambda vs body = noLocA $ HsLam noAnn LamSingle (MG (Generated OtherExpansion DoPmc) (noLocA [noLocA $ Match noExtField (LamAlt LamSingle) (L noSpanAnchor vs) (GRHSs emptyComments [noLocA $ GRHS noAnn [] body] (EmptyLocalBinds noExtField))]))
 
 -- | 'paren e' wraps 'e' in parens if 'e' is non-atomic.
 paren :: LHsExpr GhcPs -> LHsExpr GhcPs
@@ -70,7 +71,7 @@
 
 
 apps :: [LHsExpr GhcPs] -> LHsExpr GhcPs
-apps = foldl1' mkApp where mkApp x y = noLoc (HsApp noExtField x y)
+apps = foldl1' mkApp where mkApp x y = noLocA (HsApp noExtField x y)
 
 fromApps :: LHsExpr GhcPs  -> [LHsExpr GhcPs]
 fromApps (L _ (HsApp _ x y)) = fromApps x ++ [y]
@@ -84,17 +85,20 @@
 universeApps x = x : concatMap universeApps (childrenApps x)
 
 descendAppsM :: Monad m => (LHsExpr GhcPs  -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs)
-descendAppsM f (L l (HsApp _ x y)) = liftA2 (\x y -> L l $ HsApp noExtField x y) (descendAppsM f x) (f y)
+descendAppsM f (L l (HsApp _ x y)) = (\x y -> L l $ HsApp noExtField x y) <$> descendAppsM f x <*> f y
 descendAppsM f x = descendM f x
 
 transformAppsM :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs)
 transformAppsM f x = f =<< descendAppsM (transformAppsM f) x
 
 descendIndex :: Data a => (Int -> a -> a) -> a -> a
-descendIndex f x = flip evalState 0 $ flip descendM x $ \y -> do
+descendIndex f = fst . descendIndex' (\x a -> writer (f x a, ()))
+
+descendIndex' :: (Data a, Monoid w) => (Int -> a -> Writer w a) -> a -> (a, w)
+descendIndex' f x = runWriter $ flip evalStateT 0 $ flip descendM x $ \y -> do
     i <- get
     modify (+1)
-    pure $ f i y
+    lift $ f i y
 
 --  There are differences in pretty-printing between GHC and HSE. This
 --  version never removes brackets.
@@ -112,16 +116,15 @@
 -- A list of application, with any necessary brackets.
 appsBracket :: [LHsExpr GhcPs] -> LHsExpr GhcPs
 appsBracket = foldl1 mkApp
-  where mkApp x y = rebracket1 (noLoc $ HsApp noExtField x y)
-
+  where mkApp x y = rebracket1 (noLocA $ HsApp noExtField x y)
 
 simplifyExp :: LHsExpr GhcPs -> LHsExpr GhcPs
 -- Replace appliciations 'f $ x' with 'f (x)'.
-simplifyExp (L l (OpApp _ x op y)) | isDol op = L l (HsApp noExtField x (noLoc (HsPar noExtField y)))
-simplifyExp e@(L _ (HsLet _ (L _ (HsValBinds _ (ValBinds _ binds []))) z)) =
+simplifyExp (L l (OpApp _ x op y)) | isDol op = L l (HsApp noExtField x (nlHsPar y))
+simplifyExp e@(L _ (HsLet _ ((HsValBinds _ (ValBinds _ binds []))) z)) =
   -- An expression of the form, 'let x = y in z'.
-  case bagToList binds of
-    [L _ (FunBind _ _(MG _ (L _ [L _ (Match _(FunRhs (L _ x) _ _) [] (GRHSs _[L _ (GRHS _ [] y)] (L _ (EmptyLocalBinds _))))]) _) _ _)]
+  case binds of
+    [L _ (FunBind _ _ (MG _ (L _ [L _ (Match _(FunRhs (L _ x) _ _ _) (L _ []) (GRHSs _ [L _ (GRHS _ [] y)] ((EmptyLocalBinds _))))])))]
          -- If 'x' is not in the free variables of 'y', beta-reduce to
          -- 'z[(y)/x]'.
       | occNameStr x `notElem` vars y && length [() | Unqual a <- universeBi z, a == rdrNameOcc x] <= 1 ->
@@ -136,7 +139,6 @@
 niceDotApp (L _ (HsVar _ (L _ r))) b | occNameStr r == "$" = b
 niceDotApp a b = dotApp a b
 
-
 -- Generate a lambda expression but prettier if possible.
 niceLambda :: [String] -> LHsExpr GhcPs -> LHsExpr GhcPs
 niceLambda ss e = fst (niceLambdaR ss e)-- We don't support refactorings yet.
@@ -174,7 +176,7 @@
   , vars e `disjoint` [v]
   , L _ (HsVar _ (L _ fname)) <- f
   , isSymOcc $ rdrNameOcc fname
-  = let res = noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField e f
+  = let res = nlHsPar $ noLocA $ SectionL noExtField e f
      in (res, \s -> [Replace Expr s [] (unsafePrettyPrint res)])
 
 -- @\vs v -> f x v@ ==> @\vs -> f x@
@@ -195,7 +197,7 @@
 -- lexeme, or it all gets too complex).
 niceLambdaR [x] (view -> App2 op@(L _ (HsVar _ (L _ tag))) l r)
   | isLexeme r, view l == Var_ x, x `notElem` vars r, allowRightSection (occNameStr tag) =
-      let e = rebracket1 $ addParen (noLoc $ SectionR noExtField op r)
+      let e = rebracket1 $ addParen (noLocA $ SectionR noExtField op r)
       in (e, \s -> [Replace Expr s [] (unsafePrettyPrint e)])
 -- Rewrite (1) @\x -> f (b x)@ as @f . b@, (2) @\x -> f $ b x@ as @f . b@.
 niceLambdaR [x] y
@@ -214,7 +216,7 @@
     factor _ = Nothing
     mkRefact :: [LHsExpr GhcPs] -> R.SrcSpan -> Refactoring R.SrcSpan
     mkRefact subts s =
-      let tempSubts = zipWith (\a b -> (a, toSS b)) substVars subts
+      let tempSubts = zipWith (\a b -> (a, toSSA b)) substVars subts
           template = dotApps (map (strToVar . fst) tempSubts)
       in Replace Expr s tempSubts (unsafePrettyPrint template)
 -- Rewrite @\x y -> x + y@ as @(+)@.
@@ -224,31 +226,32 @@
 niceLambdaR [x, y] (view -> App2 op (view -> Var_ y1) (view -> Var_ x1))
   | x == x1, y == y1, vars op `disjoint` [x, y] =
       ( gen op
-      , \s -> [Replace Expr s [("x", toSS op)] (unsafePrettyPrint $ gen (strToVar "x"))]
+      , \s -> [Replace Expr s [("x", toSSA op)] (unsafePrettyPrint $ gen (strToVar "x"))]
       )
   where
-    gen = noLoc . HsApp noExtField (strToVar "flip")
+    gen :: LHsExpr GhcPs -> LHsExpr GhcPs
+    gen = noLocA . HsApp noExtField (strToVar "flip")
         . if isAtom op then id else addParen
 
 -- We're done factoring, but have no variables left, so we shouldn't make a lambda.
 -- @\ -> e@ ==> @e@
-niceLambdaR [] e = (e, \s -> [Replace Expr s [("a", toSS e)] "a"])
+niceLambdaR [] e = (e, \s -> [Replace Expr s [("a", toSSA e)] "a"])
 -- Base case. Just a good old fashioned lambda.
 niceLambdaR ss e =
-  let grhs = noLoc $ GRHS noExtField [] e :: LGRHS GhcPs (LHsExpr GhcPs)
-      grhss = GRHSs {grhssExt = noExtField, grhssGRHSs=[grhs], grhssLocalBinds=noLoc $ EmptyLocalBinds noExtField}
-      match = noLoc $ Match {m_ext=noExtField, m_ctxt=LambdaExpr, m_pats=map strToPat ss, m_grhss=grhss} :: LMatch GhcPs (LHsExpr GhcPs)
-      matchGroup = MG {mg_ext=noExtField, mg_origin=Generated, mg_alts=noLoc [match]}
-  in (noLoc $ HsLam noExtField matchGroup, const [])
+  let grhs = noLocA $ GRHS noAnn [] e :: LGRHS GhcPs (LHsExpr GhcPs)
+      grhss = GRHSs {grhssExt = emptyComments, grhssGRHSs=[grhs], grhssLocalBinds=EmptyLocalBinds noExtField}
+      match = noLocA $ Match {m_ext=noExtField, m_ctxt=LamAlt LamSingle, m_pats=noLocA $ map strToPat ss, m_grhss=grhss} :: LMatch GhcPs (LHsExpr GhcPs)
+      matchGroup = MG {mg_ext=Generated OtherExpansion SkipPmc, mg_alts=noLocA [match]}
+  in (noLocA $ HsLam noAnn LamSingle matchGroup, const [])
 
 
 -- 'case' and 'if' expressions have branches, nothing else does (this
 -- doesn't consider 'HsMultiIf' perhaps it should?).
 replaceBranches :: LHsExpr GhcPs -> ([LHsExpr GhcPs], [LHsExpr GhcPs] -> LHsExpr GhcPs)
-replaceBranches (L l (HsIf _ _ a b c)) = ([b, c], \[b, c] -> cL l (HsIf noExtField Nothing a b c))
+replaceBranches (L l (HsIf _ a b c)) = ([b, c], \[b, c] -> L l (HsIf noAnn a b c))
 
-replaceBranches (L s (HsCase _ a (MG _ (L l bs) FromSource))) =
-  (concatMap f bs, \xs -> cL s (HsCase noExtField a (MG noExtField (cL l (g bs xs)) Generated)))
+replaceBranches (L s (HsCase _ a (MG FromSource (L l bs)))) =
+  (concatMap f bs, L s . HsCase noAnn a . MG (Generated OtherExpansion SkipPmc). L l . g bs)
   where
     f :: LMatch GhcPs (LHsExpr GhcPs) -> [LHsExpr GhcPs]
     f (L _ (Match _ CaseAlt _ (GRHSs _ xs _))) = [x | (L _ (GRHS _ _ x)) <- xs]
@@ -256,7 +259,7 @@
 
     g :: [LMatch GhcPs (LHsExpr GhcPs)] -> [LHsExpr GhcPs] -> [LMatch GhcPs (LHsExpr GhcPs)]
     g (L s1 (Match _ CaseAlt a (GRHSs _ ns b)) : rest) xs =
-      cL s1 (Match noExtField CaseAlt a (GRHSs noExtField [cL a (GRHS noExtField gs x) | (L a (GRHS _ gs _), x) <- zip ns as] b)) : g rest bs
+      L s1 (Match noExtField CaseAlt a (GRHSs emptyComments [L a (GRHS noAnn gs x) | (L a (GRHS _ gs _), x) <- zip ns as] b)) : g rest bs
       where  (as, bs) = splitAt (length ns) xs
     g [] [] = []
     g _ _ = error "GHC.Util.HsExpr.replaceBranches': internal invariant failed, lists are of differing lengths"
@@ -271,37 +274,45 @@
   | isDotApp parent, isDotApp child, i == 2 = False
   | otherwise = needBracket i parent child
 
-transformBracketOld :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs)
+transformBracketOld :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs))
+                    -> LHsExpr GhcPs
+                    -> (LHsExpr GhcPs, (LHsExpr GhcPs, [String]))
 transformBracketOld op = first snd . g
   where
     g = first f . descendBracketOld g
     f x = maybe (False, x) (True, ) (op x)
 
 -- Descend, and if something changes then add/remove brackets
--- appropriately. Returns (suggested replacement, refactor template).
--- Whenever a bracket is added to the suggested replacement, a
--- corresponding bracket is added to the refactor template.
-descendBracketOld :: (LHsExpr GhcPs -> ((Bool, LHsExpr GhcPs), LHsExpr GhcPs))
-                   -> LHsExpr GhcPs
-                   -> (LHsExpr GhcPs, LHsExpr GhcPs)
-descendBracketOld op x = (descendIndex g1 x, descendIndex g2 x)
+-- appropriately. Returns (suggested replacement, (refactor template, no bracket vars)),
+-- where "no bracket vars" is a list of substitution variables which, when expanded,
+-- should have the brackets stripped.
+descendBracketOld :: (LHsExpr GhcPs -> ((Bool, LHsExpr GhcPs), (LHsExpr GhcPs, [String])))
+                  -> LHsExpr GhcPs
+                  -> (LHsExpr GhcPs, (LHsExpr GhcPs, [String]))
+descendBracketOld op x = (descendIndex g1 x, descendIndex' g2 x)
   where
-    g i y = if a then (f1 i b z, f2 i b z) else (b, z)
-      where ((a, b), z) = op y
+    g i y = if a then (f1 i b z w, f2 i b z w) else (b, (z, w))
+      where ((a, b), (z, w)) = op y
 
-    g1 = (fst .) . g
-    g2 = (snd .) . g
+    g1 a b = fst (g a b)
+    g2 a b = writer $ snd (g a b)
 
-    f i (L _ (HsPar _ y)) z
-      | not $ needBracketOld i x y = (y, z)
-    f i y z
-      | needBracketOld i x y = (addParen y, addParen z)
+    f i (L _ (HsPar _ y)) z w
+      | not $ needBracketOld i x y = (y, removeBracket z)
+      where
+        -- If the template expr is a Var, record it so that we can remove the brackets
+        -- later when expanding it. Otherwise, remove the enclosing brackets (if any).
+        removeBracket = \case
+          var@(L _ HsVar{}) -> (z, varToStr var : w)
+          other -> (fromParen z, w)
+    f i y z w
+      | needBracketOld i x y = (addParen y, (addParen z, w))
       -- https://github.com/mpickering/apply-refact/issues/7
-      | isOp y = (y, addParen z)
-    f _ y z                 = (y, z)
+      | isOp y = (y, (addParen z, w))
+    f _ y z w = (y, (z, w))
 
-    f1 = ((fst .) .) . f
-    f2 = ((snd .) .) . f
+    f1 a b c d = fst (f a b c d)
+    f2 a b c d = snd (f a b c d)
 
     isOp = \case
       L _ (HsVar _ (L _ name)) -> isSymbolRdrName name
diff --git a/src/GHC/Util/Scope.hs b/src/GHC/Util/Scope.hs
--- a/src/GHC/Util/Scope.hs
+++ b/src/GHC/Util/Scope.hs
@@ -1,5 +1,5 @@
 
-{-# LANGUAGE ViewPatterns #-}
+{-# Language ViewPatterns #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 module GHC.Util.Scope (
@@ -8,18 +8,19 @@
 ) where
 
 import GHC.Hs
-import SrcLoc
-import BasicTypes
-import Module
-import FastString
-import RdrName
-import OccName
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
+import GHC.Data.FastString
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
+import GHC.Types.PkgQual
 
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 import Data.List.Extra
 import Data.Maybe
+import Data.Bifunctor
 
 -- A scope is a list of import declarations.
 newtype Scope = Scope [LImportDecl GhcPs]
@@ -34,15 +35,20 @@
   where
     -- Package qualifier of an import declaration.
     pkg :: LImportDecl GhcPs -> Maybe StringLiteral
-    pkg (L _ x) = ideclPkgQual x
+    pkg (L _ x) =
+      case ideclPkgQual x of
+        RawPkgQual s -> Just s
+        NoRawPkgQual -> Nothing
 
-    -- The import declaraions contained by the module 'xs'.
+    -- The import declarations contained by the module 'xs'.
     res :: [LImportDecl GhcPs]
-    res = [x | x <- hsmodImports xs , pkg x /= Just (StringLiteral NoSourceText (fsLit "hint"))]
+    res = [x | x <- hsmodImports xs
+             , pkg x /= Just (StringLiteral NoSourceText (fsLit "hint") Nothing)
+          ]
 
-    -- Mock up an import declaraion corresponding to 'import Prelude'.
+    -- Mock up an import declaration corresponding to 'import Prelude'.
     prelude :: LImportDecl GhcPs
-    prelude = noLoc $ simpleImportDecl (mkModuleName "Prelude")
+    prelude = noLocA $ simpleImportDecl (mkModuleName "Prelude")
 
     -- Predicate to test for a 'Prelude' import declaration.
     isPrelude :: LImportDecl GhcPs -> Bool
@@ -52,7 +58,7 @@
 -- thing. This is the case if the names are equal and (1) denote a
 -- builtin type or data constructor or (2) the intersection of the
 -- candidate modules where the two names arise is non-empty.
-scopeMatch :: (Scope, Located RdrName) -> (Scope, Located RdrName) -> Bool
+scopeMatch :: (Scope, LocatedN RdrName) -> (Scope, LocatedN RdrName) -> Bool
 scopeMatch (a, x) (b, y)
   | isSpecial x && isSpecial y = rdrNameStr x == rdrNameStr y
   | isSpecial x || isSpecial y = False
@@ -62,60 +68,75 @@
 -- Given a name in a scope, and a new scope, create a name for the new
 -- scope that will refer to the same thing. If the resulting name is
 -- ambiguous, pick a plausible candidate.
-scopeMove :: (Scope, Located RdrName) -> Scope -> Located RdrName
+scopeMove :: (Scope, LocatedN RdrName) -> Scope -> LocatedN RdrName
 scopeMove (a, x@(fromQual -> Just name)) (Scope b) = case imps of
-  [] -> headDef x real
-  imp:_ | all (\x -> ideclQualified x /= NotQualified) imps -> noLoc $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name
+  [] | -- If `possModules a x` includes Prelude, but `b` does not contain any module that may import `x`,
+       -- then unqualify `x` and assume that it is from Prelude (#1298).
+       any (\(L _ x) -> (moduleNameString . fst <$> isQual_maybe x) == Just "Prelude") real -> unqual x
+     | otherwise -> headDef x real
+  imp:_ | all (\x -> ideclQualified x /= NotQualified) imps -> noLocA $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name
         | otherwise -> unqual x
   where
-    real :: [Located RdrName]
-    real = [noLoc $ mkRdrQual m name | m <- possModules a x]
+    real :: [LocatedN RdrName]
+    real = [noLocA $ mkRdrQual m name | m <- possModules a x]
 
     imps :: [ImportDecl GhcPs]
-    imps = [unLoc i | r <- real, i <- b, possImport i r]
+    imps = [unLoc i | r <- real, i <- b, possImport i r /= NotImported]
 scopeMove (_, x) _ = x
 
 -- Calculate which modules a name could possibly lie in. If 'x' is
 -- qualified but no imported element matches it, assume the user just
 -- lacks an import.
-possModules :: Scope -> Located RdrName -> [ModuleName]
-possModules (Scope is) x = f x
+-- 'prelude' is added to the result, unless we are certain which module a name is from (#1298).
+possModules :: Scope -> LocatedN RdrName -> [ModuleName]
+possModules (Scope is) x =
+    [prelude | prelude `notElem` map fst res, not (any snd res)] ++ fmap fst res
   where
-    res :: [ModuleName]
-    res = [unLoc $ ideclName $ unLoc i | i <- is, possImport i x]
+    -- The 'Bool' signals whether we are certain that 'x' is imported from the module.
+    res0, res :: [(ModuleName, Bool)]
+    res0 = [ (unLoc $ ideclName $ unLoc i, isImported == Imported)
+           | i <- is, let isImported = possImport i x, isImported /= NotImported ]
 
-    f :: Located RdrName -> [ModuleName]
-    f n | isSpecial n = [mkModuleName ""]
-    f (L _ (Qual mod _)) = [mod | null res] ++ res
-    f _ = res
+    res | isSpecial x = [(mkModuleName "", True)]
+        | L _ (Qual mod _) <- x = [(mod, True) | null res0] ++ res0
+        | otherwise = res0
 
+    prelude = mkModuleName "Prelude"
+
+data IsImported = Imported | PossiblyImported | NotImported  deriving (Eq)
+
 -- Determine if 'x' could possibly lie in the module named by the
 -- import declaration 'i'.
-possImport :: LImportDecl GhcPs -> Located RdrName -> Bool
-possImport i n | isSpecial n = False
+possImport :: LImportDecl GhcPs -> LocatedN RdrName -> IsImported
+possImport i n | isSpecial n = NotImported
 possImport (L _ i) (L _ (Qual mod x)) =
-  mod `elem` ms && possImport (noLoc i{ideclQualified=NotQualified}) (noLoc $ mkRdrUnqual x)
+  if mod `elem` ms && NotImported /= possImport (noLocA i{ideclQualified=NotQualified}) (noLocA $ mkRdrUnqual x)
+    then Imported
+    else NotImported
   where ms = map unLoc $ ideclName i : maybeToList (ideclAs i)
-possImport (L _ i) (L _ (Unqual x)) = ideclQualified i == NotQualified && maybe True f (ideclHiding i)
+possImport (L _ i) (L _ (Unqual x)) =
+  if ideclQualified i == NotQualified
+    then maybe PossiblyImported (f . first (== EverythingBut)) (ideclImportList i)
+    else NotImported
   where
-    f :: (Bool, Located [LIE GhcPs]) -> Bool
-    f (hide, L _ xs) =
-      if hide then
-        Just True `notElem` ms
-      else
-        Nothing `elem` ms || Just True `elem` ms
+    f :: (Bool, LocatedLI [LocatedA (IE GhcPs)]) -> IsImported
+    f (hide, L _ xs)
+      | hide = if Just True `elem` ms then NotImported else PossiblyImported
+      | Just True `elem` ms = Imported
+      | Nothing `elem` ms = PossiblyImported
+      | otherwise = NotImported
       where ms = map g xs
 
     tag :: String
     tag = occNameString x
 
     g :: LIE GhcPs -> Maybe Bool -- Does this import cover the name 'x'?
-    g (L _ (IEVar _ y)) = Just $ tag == unwrapName y
-    g (L _ (IEThingAbs _ y)) = Just $ tag == unwrapName y
-    g (L _ (IEThingAll _ y)) = if tag == unwrapName y then Just True else Nothing
-    g (L _ (IEThingWith _ y _wildcard ys _fields)) = Just $ tag `elem` unwrapName y : map unwrapName ys
+    g (L _ (IEVar _ y _)) = Just $ tag == unwrapName y
+    g (L _ (IEThingAbs _ y _)) = Just $ tag == unwrapName y
+    g (L _ (IEThingAll _ y _)) = if tag == unwrapName y then Just True else Nothing
+    g (L _ (IEThingWith _ y _ ys _)) = Just $ tag `elem` unwrapName y : map unwrapName ys
     g _ = Just False
 
-    unwrapName :: LIEWrappedName RdrName -> String
+    unwrapName :: LIEWrappedName GhcPs -> String
     unwrapName x = occNameString (rdrNameOcc $ ieWrappedName (unLoc x))
-possImport _ _ = False
+possImport _ _ = NotImported
diff --git a/src/GHC/Util/SrcLoc.hs b/src/GHC/Util/SrcLoc.hs
--- a/src/GHC/Util/SrcLoc.hs
+++ b/src/GHC/Util/SrcLoc.hs
@@ -1,23 +1,64 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 module GHC.Util.SrcLoc (
-    stripLocs
+    getAncLoc
+  , stripLocs
   , SrcSpanD(..)
   ) where
 
-import SrcLoc
-import Outputable
+import GHC.Parser.Annotation
+import GHC.Types.SrcLoc
+import GHC.Utils.Outputable
+import GHC.Data.FastString
+import GHC.Data.Strict qualified
 
 import Data.Default
 import Data.Data
 import Data.Generics.Uniplate.DataOnly
 
+-- Get the 'SrcSpan' out of a value located by an 'NoCommentsLocation'
+-- (e.g. comments).
+getAncLoc :: GenLocated NoCommentsLocation a -> SrcSpan
+getAncLoc o = RealSrcSpan (GHC.Parser.Annotation.epaLocationRealSrcSpan (GHC.Types.SrcLoc.getLoc o)) GHC.Data.Strict.Nothing
+
 -- 'stripLocs x' is 'x' with all contained source locs replaced by
 -- 'noSrcSpan'.
-stripLocs :: (Data from, HasSrcSpan from) => from -> from
-stripLocs = transformBi (const noSrcSpan)
+stripLocs :: Data from => from -> from
+stripLocs =
+  transformBi (const dummySpan) . transformBi (const noSrcSpan)
+  where
+    dummyLoc = mkRealSrcLoc (fsLit "dummy") 1 1
+    dummySpan = mkRealSrcSpan dummyLoc dummyLoc
 
--- 'Duplicates.hs' requires 'SrcSpan' be in 'Default'.
+-- TODO (2020-10-03, SF): Maybe move the following definitions down to
+-- ghc-lib-parser at some point.
+
+-- 'Duplicates.hs' requires 'SrcSpan' be in 'Default' and 'Ord'.
 newtype SrcSpanD = SrcSpanD SrcSpan
-  deriving (Outputable, Eq, Ord)
+  deriving (Outputable, Eq)
 instance Default SrcSpanD where def = SrcSpanD noSrcSpan
+
+newtype FastStringD = FastStringD FastString
+  deriving Eq
+compareFastStrings (FastStringD f) (FastStringD g) =
+  lexicalCompareFS f g
+instance Ord FastStringD where compare = compareFastStrings
+
+-- SrcSpan no longer provides 'Ord' so we are forced to roll our own.
+--
+-- Note: This implementation chooses that any span compares 'EQ to an
+-- 'UnhelpfulSpan'. Ex falso quodlibet!
+compareSrcSpans (SrcSpanD a) (SrcSpanD b) =
+  case a of
+    RealSrcSpan a1 _ ->
+      case b of
+        RealSrcSpan b1 _ ->
+          a1 `compareRealSrcSpans` b1
+        _ -> EQ -- error "'Duplicate.hs' invariant error: can't compare unhelpful spans"
+    _ -> EQ -- error "'Duplicate.hs' invariant error: can't compare unhelpful spans"
+compareRealSrcSpans a b =
+  let (a1, a2, a3, a4, a5) = (LexicalFastString (srcSpanFile a), srcSpanStartLine a, srcSpanStartCol a, srcSpanEndLine a, srcSpanEndCol a)
+      (b1, b2, b3, b4, b5) = (LexicalFastString (srcSpanFile b), srcSpanStartLine b, srcSpanStartCol b, srcSpanEndLine b, srcSpanEndCol b)
+  in compare (a1, a2, a3, a4, a5) (b1, b2, b3, b4, b5)
+instance Ord SrcSpanD where compare = compareSrcSpans
diff --git a/src/GHC/Util/Unify.hs b/src/GHC/Util/Unify.hs
--- a/src/GHC/Util/Unify.hs
+++ b/src/GHC/Util/Unify.hs
@@ -1,8 +1,9 @@
 {-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables, TupleSections #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-}
+{-# LANGUAGE DataKinds #-}
 
 module GHC.Util.Unify(
-    Subst, fromSubst,
+    Subst(..), fromSubst,
     validSubst, removeParens, substitute,
     unifyExp
     ) where
@@ -16,9 +17,9 @@
 import Util
 
 import GHC.Hs
-import SrcLoc
-import Outputable hiding ((<>))
-import RdrName
+import GHC.Types.SrcLoc
+import GHC.Utils.Outputable hiding ((<>))
+import GHC.Types.Name.Reader
 
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
@@ -27,7 +28,7 @@
 import GHC.Util.HsExpr
 import GHC.Util.View
 import Data.Maybe
-import FastString
+import GHC.Data.FastString
 
 isUnifyVar :: String -> Bool
 isUnifyVar [x] = x == '?' || isAlpha x
@@ -61,11 +62,15 @@
 removeParens noParens (Subst xs) = Subst $
   map (\(x, y) -> if x `elem` noParens then (x, fromParen y) else (x, y)) xs
 
--- Peform a substition.
--- Returns (suggested replacement, refactor template), both with brackets added
--- as needed.
--- Example: (traverse foo (bar baz), traverse f (x))
-substitute :: Subst (LHsExpr GhcPs) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs)
+-- Perform a substitution.
+-- Returns (suggested replacement, (refactor template, no bracket vars)). It adds/removes brackets
+-- for both the suggested replacement and the refactor template appropriately. The "no bracket vars"
+-- is a list of substitution variables which, when expanded, should have the brackets stripped.
+--
+-- Examples:
+--   (traverse foo (bar baz), (traverse f (x), []))
+--   (zipWith foo bar baz, (f a b, [f]))
+substitute :: Subst (LHsExpr GhcPs) -> LHsExpr GhcPs -> (LHsExpr GhcPs, (LHsExpr GhcPs, [String]))
 substitute (Subst bind) = transformBracketOld exp . transformBi pat . transformBi typ
   where
     exp :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)
@@ -73,13 +78,13 @@
     exp (L _ (HsVar _ x)) = lookup (rdrNameStr x) bind
     -- Operator applications.
     exp (L loc (OpApp _ lhs (L _ (HsVar _ x)) rhs))
-      | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (OpApp noExtField lhs y rhs))
+      | Just y <- lookup (rdrNameStr x) bind = Just (L loc (OpApp noExtField lhs y rhs))
     -- Left sections.
     exp (L loc (SectionL _ exp (L _ (HsVar _ x))))
-      | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (SectionL noExtField exp y))
+      | Just y <- lookup (rdrNameStr x) bind = Just (L loc (SectionL noExtField exp y))
     -- Right sections.
     exp (L loc (SectionR _ (L _ (HsVar _ x)) exp))
-      | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (SectionR noExtField y exp))
+      | Just y <- lookup (rdrNameStr x) bind = Just (L loc (SectionR noExtField y exp))
     exp _ = Nothing
 
     pat :: LPat GhcPs -> LPat GhcPs
@@ -98,7 +103,7 @@
 ---------------------------------------------------------------------
 -- UNIFICATION
 
-type NameMatch = Located RdrName -> Located RdrName -> Bool
+type NameMatch = LocatedN RdrName -> LocatedN RdrName -> Bool
 
 -- | Unification, obeys the property that if @unify a b = s@, then
 -- @substitute s a = b@.
@@ -108,11 +113,46 @@
     | Just (x, y) <- cast (x, y) = unifyPat' nm x y
     | Just (x, y) <- cast (x, y) = unifyType' nm x y
     | Just (x, y) <- cast (x, y) = if (x :: FastString) == y then Just mempty else Nothing
-    | Just (x :: SrcSpan) <- cast x = Just mempty
+
+    -- We need some type magic to reduce this.
+    | Just (x :: EpAnn EpaLocation) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnContext) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnExplicitSum) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnFieldLabel) <- cast x = Just mempty
+    | Just (x :: EpAnn (AnnList [EpToken ","])) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnListItem) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnParen) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnPragma) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnProjection) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnsIf) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnSig) <- cast x = Just mempty
+    | Just (x :: EpAnn AnnsModule) <- cast x = Just mempty
+    | Just (x :: EpAnn EpAnnHsCase) <- cast x = Just mempty
+    | Just (x :: EpAnn EpAnnImportDecl) <- cast x = Just mempty
+    | Just (x :: EpAnn EpAnnSumPat) <- cast x = Just mempty
+    | Just (x :: EpAnn EpAnnUnboundVar) <- cast x = Just mempty
+    | Just (x :: EpAnn GrhsAnn) <- cast x = Just mempty
+    | Just (x :: EpAnn HsRuleAnn) <- cast x = Just mempty
+    | Just (x :: EpAnn NameAnn) <- cast x = Just mempty
+    | Just (x :: EpAnn NoEpAnns) <- cast x = Just mempty
+    | Just (x :: EpToken "let") <- cast x = Just mempty
+    | Just (x :: EpToken "in") <- cast x = Just mempty
+    | Just (x :: EpToken "@") <- cast x = Just mempty
+    | Just (x :: EpToken "(") <- cast x = Just mempty
+    | Just (x :: EpToken ")") <- cast x = Just mempty
+    | Just (x :: EpToken "type") <- cast x = Just mempty
+    | Just (x :: EpToken "%") <- cast x = Just mempty
+    | Just (x :: EpToken "%1") <- cast x = Just mempty
+    | Just (x :: EpToken "⊸") <- cast x = Just mempty
+    | Just (x :: EpUniToken "->" "→") <- cast x = Just mempty
+    | Just (x :: TokenLocation) <- cast y = Just mempty
+    | Just (y :: SrcSpan) <- cast y = Just mempty
+
     | otherwise = unifyDef' nm x y
 
 unifyDef' :: Data a => NameMatch -> a -> a -> Maybe (Subst (LHsExpr GhcPs))
-unifyDef' nm x y = fmap mconcat . sequence =<< gzip (unify' nm False) x y
+unifyDef' nm x y =
+  fmap mconcat . sequence =<< gzip (unify' nm False) x y
 
 unifyComposed' :: NameMatch
                -> LHsExpr GhcPs
@@ -122,7 +162,7 @@
   ((, Just y11) <$> unifyExp' nm False x1 y12)
     <|> case y12 of
           (L _ (OpApp _ y121 dot' y122)) | isDot dot' ->
-            unifyComposed' nm x1 (noLoc (OpApp noExtField y11 dot y121)) dot' y122
+            unifyComposed' nm x1 (noLocA (OpApp noExtField y11 dot y121)) dot' y122
           _ -> Nothing
 
 -- unifyExp handles the cases where both x and y are HsApp, or y is OpApp. Otherwise,
@@ -156,7 +196,7 @@
               -- Attempt #1: rewrite '(fun1 . fun2) arg' as 'fun1 (fun2 arg)', and unify it with 'x'.
               -- The guard ensures that you don't get duplicate matches because the matching engine
               -- auto-generates hints in dot-form.
-              (, Nothing) <$> unifyExp' nm root x (noLoc (HsApp noExtField y11 (noLoc (HsApp noExtField y12 y2))))
+              (, Nothing) <$> unifyExp' nm root x (noLocA (HsApp noExtField y11 (noLocA (HsApp noExtField y12 y2))))
           else do
               -- Attempt #2: rewrite '(fun1 . fun2 ... funn) arg' as 'fun1 $ (fun2 ... funn) arg',
               -- 'fun1 . fun2 $ (fun3 ... funn) arg', 'fun1 . fun2 . fun3 $ (fun4 ... funn) arg',
@@ -171,9 +211,9 @@
 unifyExp nm root x (L _ (OpApp _ lhs2 op2@(L _ (HsVar _ op2')) rhs2))
     | (L _ (OpApp _ lhs1 op1@(L _ (HsVar _ op1')) rhs1)) <- x =
         guard (nm op1' op2') >> (, Nothing) <$> liftA2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2)
-    | isDol op2 = unifyExp nm root x $ noLoc (HsApp noExtField lhs2 rhs2)
-    | isAmp op2 = unifyExp nm root x $ noLoc (HsApp noExtField rhs2 lhs2)
-    | otherwise  = unifyExp nm root x $ noLoc (HsApp noExtField (noLoc (HsApp noExtField op2 (addPar lhs2))) (addPar rhs2))
+    | isDol op2 = unifyExp nm root x $ noLocA (HsApp noExtField lhs2 rhs2)
+    | isAmp op2 = unifyExp nm root x $ noLocA (HsApp noExtField rhs2 lhs2)
+    | otherwise  = unifyExp nm root x $ noLocA (HsApp noExtField (noLocA (HsApp noExtField op2 (addPar lhs2))) (addPar rhs2))
         where
           -- add parens around when desugaring the expression, if necessary
           addPar :: LHsExpr GhcPs -> LHsExpr GhcPs
@@ -183,6 +223,7 @@
 
 isAmp :: LHsExpr GhcPs -> Bool
 isAmp (L _ (HsVar _ x)) = rdrNameStr x == "&"
+isAmp _ = False
 
 -- | If we "throw away" the extra than we have no where to put it, and the substitution is wrong
 noExtra :: Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) -> Maybe (Subst (LHsExpr GhcPs))
@@ -194,7 +235,7 @@
 -- dot at the root, since otherwise you get two matches because of
 -- 'readRule' (Bug #570).
 unifyExp' :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs))
--- Don't subsitute for type apps, since no one writes rules imagining
+-- Don't substitute for type apps, since no one writes rules imagining
 -- they exist.
 unifyExp' nm root (L _ (HsVar _ (rdrNameStr -> v))) y | isUnifyVar v, not $ isTypeApp y = Just $ Subst [(v, y)]
 unifyExp' nm root (L _ (HsVar _ x)) (L _ (HsVar _ y)) | nm x y = Just mempty
@@ -224,6 +265,10 @@
 unifyExp' nm root x y@(L _ (OpApp _ lhs2 op2@(L _ (HsVar _ op2')) rhs2)) =
   noExtra $ unifyExp nm root x y
 
+unifyExp' nm root (L _ (HsUntypedBracket _ (VarBr _ b0 (occNameStr . unLoc -> v1))))
+                  (L _ (HsUntypedBracket _ (VarBr _ b1 (occNameStr . unLoc -> v2))))
+    | b0 == b1 && isUnifyVar v1 = Just (Subst [(v1, strToVar v2)])
+
 unifyExp' nm root x y | isOther x, isOther y = unifyDef' nm x y
     where
         -- Types that are not already handled in unify.
@@ -242,7 +287,7 @@
   Just $ Subst [(rdrNameStr x, strToVar(rdrNameStr y))]
 unifyPat' nm (L _ (VarPat _ x)) (L _ (WildPat _)) =
   let s = rdrNameStr x in Just $ Subst [(s, strToVar("_" ++ s))]
-unifyPat' nm (L _ (ConPatIn x _)) (L _ (ConPatIn y _)) | rdrNameStr x /= rdrNameStr y =
+unifyPat' nm (L _ (ConPat _ x _)) (L _ (ConPat _ y _)) | rdrNameStr x /= rdrNameStr y =
   Nothing
 unifyPat' nm x y =
   unifyDef' nm x y
@@ -250,7 +295,7 @@
 unifyType' :: NameMatch -> LHsType GhcPs -> LHsType GhcPs -> Maybe (Subst (LHsExpr GhcPs))
 unifyType' nm (L loc (HsTyVar _ _ x)) y =
   let wc = HsWC noExtField y :: LHsWcType (NoGhcTc GhcPs)
-      unused = strToVar "__unused__" :: LHsExpr GhcPs
-      appType = cL loc (HsAppType noExtField unused wc) :: LHsExpr GhcPs
+      unused = strToVar "__unused__"
+      appType = L loc (HsAppType noAnn unused wc)
  in Just $ Subst [(rdrNameStr x, appType)]
 unifyType' nm x y = unifyDef' nm x y
diff --git a/src/GHC/Util/View.hs b/src/GHC/Util/View.hs
--- a/src/GHC/Util/View.hs
+++ b/src/GHC/Util/View.hs
@@ -8,58 +8,58 @@
 ) where
 
 import GHC.Hs
-import RdrName
-import SrcLoc
-import BasicTypes
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
+import GHC.Types.Basic
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import GHC.Util.Brackets
 
-fromParen :: LHsExpr GhcPs -> LHsExpr GhcPs
+fromParen :: LocatedA (HsExpr GhcPs) -> LocatedA (HsExpr GhcPs)
 fromParen x = maybe x fromParen $ remParen x
 
-fromPParen :: LPat GhcPs -> LPat GhcPs
+fromPParen :: LocatedA (Pat GhcPs) -> LocatedA (Pat GhcPs)
 fromPParen (L _ (ParPat _ x)) = fromPParen x
 fromPParen x = x
 
 class View a b where
   view :: a -> b
 
-data RdrName_ = NoRdrName_ | RdrName_ (Located RdrName)
+data RdrName_ = NoRdrName_ | RdrName_ (LocatedN RdrName)
 data Var_  = NoVar_ | Var_ String deriving Eq
 data PVar_ = NoPVar_ | PVar_ String
-data PApp_ = NoPApp_ | PApp_ String [LPat GhcPs]
-data App2  = NoApp2  | App2 (LHsExpr GhcPs) (LHsExpr GhcPs) (LHsExpr GhcPs)
-data LamConst1 = NoLamConst1 | LamConst1 (LHsExpr GhcPs)
+data PApp_ = NoPApp_ | PApp_ String [LocatedA (Pat GhcPs)]
+data App2  = NoApp2  | App2 (LocatedA (HsExpr GhcPs)) (LocatedA (HsExpr GhcPs)) (LocatedA (HsExpr GhcPs))
+data LamConst1 = NoLamConst1 | LamConst1 (LocatedA (HsExpr GhcPs))
 
-instance View (LHsExpr GhcPs) LamConst1 where
-  view (fromParen -> (L _ (HsLam _ (MG _ (L _ [L _ (Match _ LambdaExpr [L _ WildPat {}]
-    (GRHSs _ [L _ (GRHS _ [] x)] (L _ (EmptyLocalBinds _))))]) FromSource)))) = LamConst1 x
+instance View (LocatedA (HsExpr GhcPs)) LamConst1 where
+  view (fromParen -> (L _ (HsLam _ _ (MG FromSource (L _ [L _ (Match _ (LamAlt _) (L _ [L _ WildPat {}])
+    (GRHSs _ [L _ (GRHS _ [] x)] ((EmptyLocalBinds _))))]))))) = LamConst1 x
   view _ = NoLamConst1
 
-instance View (LHsExpr GhcPs) RdrName_ where
+instance View (LocatedA (HsExpr GhcPs)) RdrName_ where
     view (fromParen -> (L _ (HsVar _ name))) = RdrName_ name
     view _ = NoRdrName_
 
-instance View (LHsExpr GhcPs) Var_ where
+instance View (LocatedA (HsExpr GhcPs)) Var_ where
     view (view -> RdrName_ name) = Var_ (rdrNameStr name)
     view _ = NoVar_
 
-instance View (LHsExpr GhcPs) App2 where
+instance View (LocatedA (HsExpr GhcPs)) App2 where
   view (fromParen -> L _ (OpApp _ lhs op rhs)) = App2 op lhs rhs
   view (fromParen -> L _ (HsApp _ (L _ (HsApp _ f x)) y)) = App2 f x y
   view _ = NoApp2
 
-instance View (Located (Pat GhcPs)) PVar_ where
+instance View (LocatedA (Pat GhcPs)) PVar_ where
   view (fromPParen -> L _ (VarPat _ (L _ x))) = PVar_ $ occNameStr x
   view _ = NoPVar_
 
-instance View (Located (Pat GhcPs)) PApp_ where
-  view (fromPParen -> L _ (ConPatIn (L _ x) (PrefixCon args))) =
+instance View (LocatedA (Pat GhcPs)) PApp_ where
+  view (fromPParen -> L _ (ConPat _ (L _ x) (PrefixCon _ args))) =
     PApp_ (occNameStr x) args
-  view (fromPParen -> L _ (ConPatIn (L _ x) (InfixCon lhs rhs))) =
+  view (fromPParen -> L _ (ConPat _ (L _ x) (InfixCon lhs rhs))) =
     PApp_ (occNameStr x) [lhs, rhs]
   view _ = NoPApp_
 
 -- A lambda with no guards and no where clauses
-pattern SimpleLambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs
-pattern SimpleLambda vs body <- L _ (HsLam _ (MG _ (L _ [L _ (Match _ _ vs (GRHSs _ [L _ (GRHS _ [] body)] (L _ (EmptyLocalBinds _))))]) _))
+pattern SimpleLambda :: [LocatedA (Pat GhcPs)] -> LocatedA (HsExpr GhcPs) -> LocatedA (HsExpr GhcPs)
+pattern SimpleLambda vs body <- L _ (HsLam _ LamSingle (MG _ (L _ [L _ (Match _ _ (L _ vs) (GRHSs _ [L _ (GRHS _ [] body)] ((EmptyLocalBinds _))))])))
diff --git a/src/HLint.hs b/src/HLint.hs
--- a/src/HLint.hs
+++ b/src/HLint.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# OPTIONS_GHC -Wno-incomplete-patterns #-}
@@ -17,6 +18,7 @@
 import System.IO.Extra
 import System.Time.Extra
 import Data.Tuple.Extra
+import Data.Bifunctor (bimap)
 import Prelude
 
 import CmdLine
@@ -36,6 +38,7 @@
 import CC
 import EmbedData
 
+import SARIF qualified
 
 -- | This function takes a list of command line arguments, and returns the given hints.
 --   To see a list of arguments type @hlint --help@ at the console.
@@ -81,19 +84,37 @@
     | cmdDefault = do
         ideas <- if null cmdFiles then pure [] else withVerbosity Quiet $
             runHlintMain args cmd{cmdJson=False,cmdSerialise=False,cmdRefactor=False} Nothing
-        let bad = nubOrd $ map ideaHint ideas
+        let bad = group $ sort $ map ideaHint ideas
         if null bad then putStr defaultYaml else do
             let group1:groups = splitOn ["",""] $ lines defaultYaml
             let group2 = "# Warnings currently triggered by your code" :
-                         ["- ignore: {name: " ++ show x ++ "}" | x <- bad]
+                         ["- ignore: {name: " ++ show x ++ "} # "
+                         ++ if null tl then "1 hint" else show (length xs) ++ " hints"
+                         | xs@(x : tl) <- bad
+                         ]
+
             putStr $ unlines $ intercalate ["",""] $ group1:group2:groups
         pure []
-    | cmdGenerateSummary /= [] = do
-        forM_ cmdGenerateSummary $ \file -> timedIO "Summary" file $ do
-            whenNormal $ putStrLn $ "Writing summary to " ++ file ++ " ..."
-            summary <- generateSummary . snd =<< readAllSettings args cmd
+    | cmdGenerateMdSummary /= [] = do
+        forM_ cmdGenerateMdSummary $ \file -> timedIO "Summary" file $ do
+            whenNormal $ putStrLn $ "Writing Markdown summary to " ++ file ++ " ..."
+            summary <- generateMdSummary . snd =<< readAllSettings args cmd
             writeFileBinary file summary
         pure []
+    | cmdGenerateJsonSummary /= [] = do
+        forM_ cmdGenerateJsonSummary $ \file -> timedIO "Summary" file $ do
+            whenNormal $ putStrLn $ "Writing JSON summary to " ++ file ++ " ..."
+            summary <- generateJsonSummary . snd =<< readAllSettings args cmd
+            writeFileBinary file summary
+        pure []
+    | cmdGenerateExhaustiveConf /= [] = do
+        forM_ cmdGenerateExhaustiveConf $ \severity ->
+            let file = show severity ++ "-all.yaml"
+             in timedIO "Exhaustive config file" file $ do
+                whenNormal $ putStrLn $ "Writing " ++ show severity ++ "-all list to " ++ file ++ " ..."
+                exhaustiveConfig <- generateExhaustiveConfig severity . snd =<< readAllSettings args cmd
+                writeFileBinary file exhaustiveConfig
+        pure []
     | null cmdFiles && not (null cmdFindHints) = do
         hints <- concatMapM (resolveFile cmd Nothing) cmdFindHints
         mapM_ (putStrLn . fst <=< computeSettings (cmdParseFlags cmd)) hints >> pure []
@@ -149,12 +170,13 @@
 runHints args settings cmd@CmdMain{..} =
     withNumCapabilities cmdThreads $ do
         let outStrLn = whenNormal . putStrLn
-        ideas <- getIdeas cmd settings
-        ideas <- pure $ if cmdShowAll then ideas else  filter (\i -> ideaSeverity i /= Ignore) ideas
+        ideas <- filterIdeas <$> getIdeas cmd settings
         if cmdJson then
             putStrLn $ showIdeasJson ideas
          else if cmdCC then
             mapM_ (printIssue . fromIdea) ideas
+         else if cmdSARIF then
+            SARIF.printIdeas ideas
          else if cmdSerialise then do
             hSetBuffering stdout NoBuffering
             print $ map (show &&& ideaRefactoring) ideas
@@ -166,6 +188,12 @@
             mapM_ (outStrLn . showItem) ideas
             handleReporting ideas cmd
         pure ideas
+  where
+    filteredSeverities
+        | cmdShowAll = []
+        | cmdIgnoreSuggestions = [Ignore, Suggestion]
+        | otherwise = [Ignore]
+    filterIdeas = filter (\i -> ideaSeverity i `notElem` filteredSeverities)
 
 getIdeas :: Cmd -> [Setting] -> IO [Idea]
 getIdeas cmd@CmdMain{..} settings = do
@@ -201,8 +229,14 @@
         outStrLn $ "Writing report to " ++ x ++ " ..."
         writeReport cmdDataDir x showideas
     unless cmdNoSummary $ do
-        let n = length showideas
-        outStrLn $ if n == 0 then "No hints" else show n ++ " hint" ++ ['s' | n/=1]
+        let (nbErrors, nbHints) = bimap length length $ partition (\idea -> ideaSeverity idea == Error) showideas
+        when (nbErrors > 0) (outStrLn $ formatOutput nbErrors "error")
+        unless (nbErrors > 0 && nbHints == 0) (outStrLn $ formatOutput nbHints "hint")
+
+    where
+        formatOutput :: Int -> String -> String
+        formatOutput number name =
+            if number == 0 then "No " ++ name ++ "s" else show number ++ " " ++ name ++ ['s' | number/=1]
 
 evaluateList :: [a] -> IO [a]
 evaluateList xs = do
diff --git a/src/Hint/All.hs b/src/Hint/All.hs
--- a/src/Hint/All.hs
+++ b/src/Hint/All.hs
@@ -21,6 +21,7 @@
 import Hint.Bracket
 import Hint.Fixities
 import Hint.Naming
+import Hint.Negation
 import Hint.Pattern
 import Hint.Import
 import Hint.Export
@@ -32,14 +33,15 @@
 import Hint.Unsafe
 import Hint.NewType
 import Hint.Smell
+import Hint.NumLiteral
 
 -- | A list of the builtin hints wired into HLint.
 --   This list is likely to grow over time.
 data HintBuiltin =
-    HintList | HintListRec | HintMonad | HintLambda | HintFixities |
+    HintList | HintListRec | HintMonad | HintLambda | HintFixities | HintNegation |
     HintBracket | HintNaming | HintPattern | HintImport | HintExport |
     HintPragma | HintExtensions | HintUnsafe | HintDuplicate | HintRestrict |
-    HintComment | HintNewType | HintSmell
+    HintComment | HintNewType | HintSmell | HintNumLiteral
     deriving (Show,Eq,Ord,Bounded,Enum)
 
 -- See https://github.com/ndmitchell/hlint/issues/1150 - Duplicate is too slow
@@ -48,7 +50,6 @@
 
 builtin :: HintBuiltin -> Hint
 builtin x = case x of
-    -- Ghc.
     HintLambda     -> decl lambdaHint
     HintImport     -> modu importHint
     HintExport     -> modu exportHint
@@ -63,10 +64,12 @@
     HintNaming     -> decl namingHint
     HintBracket    -> decl bracketHint
     HintFixities   -> mempty{hintDecl=fixitiesHint}
+    HintNegation   -> decl negationParensHint
     HintSmell      -> mempty{hintDecl=smellHint,hintModule=smellModuleHint}
     HintPattern    -> decl patternHint
     HintMonad      -> decl monadHint
     HintExtensions -> modu extensionsHint
+    HintNumLiteral -> decl numLiteralHint
     where
         wrap = timed "Hint" (drop 4 $ show x) . forceList
         decl f = mempty{hintDecl=const $ \a b c -> wrap $ f a b c}
diff --git a/src/Hint/Bracket.hs b/src/Hint/Bracket.hs
--- a/src/Hint/Bracket.hs
+++ b/src/Hint/Bracket.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE TypeFamilies #-}
@@ -28,11 +29,11 @@
 main = do f; (print x) -- @Suggestion do f print x
 yes = f (x) y -- @Warning x
 no = f (+x) y
-no = f ($x) y
-no = ($x)
-yes = (($x))
-no = ($1)
-yes = (($1)) -- @Warning ($1)
+no = f ($ x) y
+no = ($ x)
+yes = (($ x))  -- @Warning ($ x)
+no = ($ 1)
+yes = (($ 1)) -- @Warning ($ 1)
 no = (+5)
 yes = ((+5)) -- @Warning (+5)
 issue909 = case 0 of { _ | n <- (0 :: Int) -> n }
@@ -42,7 +43,16 @@
 issue909 = do {((x :: y) -> z) <- e; return 1}
 issue970 = (f x +) (g x) -- f x + (g x)
 issue969 = (Just \x -> x || x) *> Just True
+issue1179 = do(this is a test) -- do this is a test
+issue1212 = $(Git.hash)
 
+-- no record dot syntax
+referenceDesignator = ReferenceDesignator (p.placementReferenceDesignator)
+
+-- record dot syntax
+{-# LANGUAGE OverloadedRecordDot #-} \
+referenceDesignator = ReferenceDesignator (p.placementReferenceDesignator) -- p.placementReferenceDesignator @NoRefactor: refactor requires GHC >= 9.2.1
+
 -- type bracket reduction
 foo :: (Int -> Int) -> Int
 foo :: (Maybe Int) -> a -- @Suggestion Maybe Int -> a
@@ -53,7 +63,6 @@
 foo (x:xs) = 1
 foo (True) = 1 -- @Warning True
 foo ((True)) = 1 -- @Warning True
-foo (A{}) = True -- A{}
 f x = case x of (Nothing) -> 1; _ -> 2 -- Nothing
 
 -- dollar reduction tests
@@ -92,125 +101,164 @@
 special = foo $ Rec{x=1}
 special = foo (f{x=1})
 loadCradleOnlyonce = skipManyTill anyMessage (message @PublishDiagnosticsNotification)
+-- These used to require a bracket
+$(pure [])
+$(x)
+-- People aren't a fan of the record constructors being secretly atomic
+function (Ctor (Rec { field })) = Ctor (Rec {field = 1})
 
 -- type splices are a bit special
 no = f @($x)
+
+-- template haskell is harder
+issue1292 = [e| handleForeignCatch $ \ $(varP pylonExPtrVarName) -> $(quoteExp C.block modifiedStr) |]
+
+-- no warnings for single-argument constraint contexts
+foo :: (A) => ()
+bar :: (A a) => ()
+foo' :: ((A) => ()) -> ()
+bar' :: ((A a) => ()) -> ()
+data Dict c where Dict :: (c) => Dict c
+data Dict' c a where Dict' :: (c a) => Dict' c a
+
+-- issue1501: Redundant bracket hint resulted in a parse error
+x = f $ \(Proxy @a) -> True
 </TEST>
 -}
 
 
 module Hint.Bracket(bracketHint) where
 
-import Hint.Type(DeclHint,Idea(..),rawIdea,warn,suggest,Severity(..),toRefactSrcSpan,toSS)
+import Hint.Type(DeclHint,Idea(..),rawIdea,warn,suggest,Severity(..),toRefactSrcSpan,toSSA)
 import Data.Data
 import Data.List.Extra
 import Data.Generics.Uniplate.DataOnly
 import Refact.Types
 
 import GHC.Hs
-import Outputable
-import SrcLoc
+import GHC.Utils.Outputable
+import GHC.Types.SrcLoc
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 
 bracketHint :: DeclHint
 bracketHint _ _ x =
-  concatMap (\x -> bracket prettyExpr isPartialAtom True x ++ dollar x) (childrenBi (descendBi annotations x) :: [LHsExpr GhcPs]) ++
-  concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [LHsType GhcPs]) ++
-  concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [LPat GhcPs]) ++
+  concatMap (\x -> bracket prettyExpr isPartialAtom True x ++ dollar x) (childrenBi (descendBi splices $ descendBi annotations x) :: [LHsExpr GhcPs]) ++
+  concatMap (bracket unsafePrettyPrint (\_ _ -> False) False) (childrenBi (preprocess x) :: [LHsType GhcPs]) ++
+  concatMap (bracket unsafePrettyPrint (\_ _ -> False) False) (childrenBi x :: [LPat GhcPs]) ++
   concatMap fieldDecl (childrenBi x)
    where
+     preprocess = transformBi removeSingleAtomConstrCtxs
+       where
+         removeSingleAtomConstrCtxs :: LHsContext GhcPs -> LHsContext GhcPs
+         removeSingleAtomConstrCtxs = fmap $ \case
+           [ty] | isAtom ty -> []
+           tys -> tys
+
      -- Brackets the roots of annotations are fine, so we strip them.
      annotations :: AnnDecl GhcPs -> AnnDecl GhcPs
      annotations= descendBi $ \x -> case (x :: LHsExpr GhcPs) of
        L _ (HsPar _ x) -> x
        x -> x
 
+     -- Brackets at the root of splices used to be required, but now they aren't
+     splices :: HsDecl GhcPs -> HsDecl GhcPs
+     splices (SpliceD a x) = SpliceD a $ flip descendBi x $ \x -> case (x :: LHsExpr GhcPs) of
+       L _ (HsPar _ x) -> x
+       x -> x
+     splices x = x
+
 -- If we find ourselves in the context of a section and we want to
--- issue a warning that a child therein has unneccessary brackets,
+-- issue a warning that a child therein has unnecessary brackets,
 -- we'd rather report 'Found : (`Foo` (Bar Baz))' rather than 'Found :
 -- `Foo` (Bar Baz)'. If left to 'unsafePrettyPrint' we'd get the
 -- latter (in contrast to the HSE pretty printer). This patches things
 -- up.
 prettyExpr :: LHsExpr GhcPs -> String
-prettyExpr s@(L _ SectionL{}) = unsafePrettyPrint (noLoc (HsPar noExtField s) :: LHsExpr GhcPs)
-prettyExpr s@(L _ SectionR{}) = unsafePrettyPrint (noLoc (HsPar noExtField s) :: LHsExpr GhcPs)
+prettyExpr s@(L _ SectionL{}) = unsafePrettyPrint (nlHsPar s :: LHsExpr GhcPs)
+prettyExpr s@(L _ SectionR{}) = unsafePrettyPrint (nlHsPar s :: LHsExpr GhcPs)
 prettyExpr x = unsafePrettyPrint x
 
 -- 'Just _' if at least one set of parens were removed. 'Nothing' if
 -- zero parens were removed.
-remParens' :: Brackets a => a -> Maybe a
+remParens' :: Brackets (LocatedA a) => LocatedA a -> Maybe (LocatedA a)
 remParens' = fmap go . remParen
   where
     go e = maybe e go (remParen e)
 
-isPartialAtom :: LHsExpr GhcPs -> Bool
+-- note(sf, 2022-06-02): i've completely bluffed my way through this.
+-- see
+-- https://gitlab.haskell.org/ghc/ghc/-/commit/7975202ba9010c581918413808ee06fbab9ac85f
+-- for where splice expressions were refactored.
+isPartialAtom :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
 -- Might be '$x', which was really '$ x', but TH enabled misparsed it.
-isPartialAtom (L _ (HsSpliceE _ (HsTypedSplice _ HasDollar _ _) )) = True
-isPartialAtom (L _ (HsSpliceE _ (HsUntypedSplice _ HasDollar _ _) )) = True
-isPartialAtom x = isRecConstr x || isRecUpdate x
+isPartialAtom _ (L _ (HsUntypedSplice _ HsUntypedSpliceExpr{})) = True
+-- Might be '$(x)' where the brackets are required in GHC 8.10 and below
+isPartialAtom (Just (L _ HsUntypedSplice{})) _ = True
+isPartialAtom _ x = isRecConstr x || isRecUpdate x
 
-bracket :: forall a . (Data a, HasSrcSpan a, Outputable a, Brackets a) => (a -> String) -> (a -> Bool) -> Bool -> a -> [Idea]
+bracket :: forall a . (Data a, Outputable a, Brackets (LocatedA a)) => (LocatedA a -> String) -> (Maybe (LocatedA a) -> LocatedA a -> Bool) -> Bool -> LocatedA a -> [Idea]
 bracket pretty isPartialAtom root = f Nothing
   where
     msg = "Redundant bracket"
     -- 'f' is a (generic) function over types in 'Brackets
     -- (expressions, patterns and types). Arguments are, 'f (Maybe
     -- (index, parent, gen)) child'.
-    f :: (HasSrcSpan a, Data a, Outputable a, Brackets a) => Maybe (Int, a , a -> a) -> a -> [Idea]
+    f :: (Data a, Outputable a, Brackets (LocatedA a)) => Maybe (Int, LocatedA a , LocatedA a -> LocatedA a) -> LocatedA a -> [Idea]
     -- No context. Removing parentheses from 'x' succeeds?
     f Nothing o@(remParens' -> Just x)
       -- If at the root, or 'x' is an atom, 'x' parens are redundant.
       | root || isAtom x
-      , not $ isPartialAtom x =
+      , not $ isPartialAtom Nothing x =
           (if isAtom x then bracketError else bracketWarning) msg o x : g x
     -- In some context, removing parentheses from 'x' succeeds and 'x'
     -- is atomic?
-    f Just{} o@(remParens' -> Just x)
+    f (Just (_, p, _)) o@(remParens' -> Just x)
       | isAtom x
-      , not $ isPartialAtom x =
+      , not $ isPartialAtom (Just p) x =
           bracketError msg o x : g x
     -- In some context, removing parentheses from 'x' succeeds. Does
     -- 'x' actually need bracketing in this context?
     f (Just (i, o, gen)) v@(remParens' -> Just x)
-      | not $ needBracket i o x, not $ isPartialAtom x =
-          rawIdea Suggestion msg (getLoc v) (pretty o) (Just (pretty (gen x))) [] [r] : g x
+      | not $ needBracket i o x
+      , not $ isPartialAtom (Just o) x
+      , not $ any isSplicePat $ universeBi o -- over-appoximate ,see #1292
+      = rawIdea Suggestion msg (getLocA v) (pretty o) (Just (pretty (gen x))) [] [r] : g x
       where
         typ = findType v
-        r = Replace typ (toSS v) [("x", toSS x)] "x"
+        r = Replace typ (toSSA v) [("x", toSSA x)] "x"
     -- Regardless of the context, there are no parentheses to remove
     -- from 'x'.
     f _ x = g x
 
-    g :: (HasSrcSpan a, Data a, Outputable a, Brackets a) => a -> [Idea]
+    g :: (Data a, Outputable a, Brackets (LocatedA a)) => LocatedA a -> [Idea]
     -- Enumerate over all the immediate children of 'o' looking for
     -- redundant parentheses in each.
     g o = concat [f (Just (i, o, gen)) x | (i, (x, gen)) <- zipFrom 0 $ holes o]
 
-bracketWarning :: (HasSrcSpan a, HasSrcSpan b, Outputable a, Outputable b, Brackets b)  => String -> a -> b -> Idea
 bracketWarning msg o x =
-  suggest msg o x [Replace (findType x) (toSS o) [("x", toSS x)] "x"]
+  suggest msg (reLoc o) (reLoc x) [Replace (findType x) (toSSA o) [("x", toSSA x)] "x"]
 
-bracketError :: (HasSrcSpan a, HasSrcSpan b, Outputable a, Outputable b, Brackets b) => String -> a -> b -> Idea
+bracketError :: (Outputable a, Outputable b, Brackets (LocatedA b)) => String -> LocatedA a -> LocatedA b -> Idea
 bracketError msg o x =
-  warn msg o x [Replace (findType x) (toSS o) [("x", toSS x)] "x"]
+  warn msg (reLoc o) (reLoc x) [Replace (findType x) (toSSA o) [("x", toSSA x)] "x"]
 
 fieldDecl ::  LConDeclField GhcPs -> [Idea]
 fieldDecl o@(L loc f@ConDeclField{cd_fld_type=v@(L l (HsParTy _ c))}) =
    let r = L loc (f{cd_fld_type=c}) :: LConDeclField GhcPs in
-   [rawIdea Suggestion "Redundant bracket" l
+   [rawIdea Suggestion "Redundant bracket" (locA l)
     (showSDocUnsafe $ ppr_fld o) -- Note this custom printer!
     (Just (showSDocUnsafe $ ppr_fld r))
     []
-    [Replace Type (toSS v) [("x", toSS c)] "x"]]
+    [Replace Type (toSSA v) [("x", toSSA c)] "x"]]
    where
      -- If we call 'unsafePrettyPrint' on a field decl, we won't like
      -- the output (e.g. "[foo, bar] :: T"). Here we use a custom
-     -- printer to work around (snarfed from
-     -- https://hackage.haskell.org/package/ghc-lib-parser-8.8.1/docs/src/HsTypes.html#pprConDeclFields).
+     -- printer to work around (snarfed from Hs.Types.pprConDeclFields)
      ppr_fld (L _ ConDeclField { cd_fld_names = ns, cd_fld_type = ty, cd_fld_doc = doc })
-       = ppr_names ns <+> dcolon <+> ppr ty <+> ppr_mbDoc doc
+       = pprMaybeWithDoc doc (ppr_names ns <+> dcolon <+> ppr ty)
      ppr_fld (L _ (XConDeclField x)) = ppr x
 
      ppr_names [n] = ppr n
@@ -222,31 +270,31 @@
 dollar :: LHsExpr GhcPs -> [Idea]
 dollar = concatMap f . universe
   where
-    f x = [ (suggest "Redundant $" x y [r]){ideaSpan = getLoc d} | L _ (OpApp _ a d b) <- [x], isDol d
-            , let y = noLoc (HsApp noExtField a b) :: LHsExpr GhcPs
+    f x = [ (suggest "Redundant $" (reLoc x) (reLoc y) [r]){ideaSpan = locA (getLoc d)} | L _ (OpApp _ a d b) <- [x], isDol d
+            , let y = noLocA (HsApp noExtField a b) :: LHsExpr GhcPs
             , not $ needBracket 0 y a
             , not $ needBracket 1 y b
-            , not $ isPartialAtom b
-            , let r = Replace Expr (toSS x) [("a", toSS a), ("b", toSS b)] "a b"]
+            , not $ isPartialAtom (Just x) b
+            , let r = Replace Expr (toSSA x) [("a", toSSA a), ("b", toSSA b)] "a b"]
           ++
-          [ suggest "Move brackets to avoid $" x (t y) [r]
+          [ suggest "Move brackets to avoid $" (reLoc x) (reLoc (t y)) [r]
             |(t, e@(L _ (HsPar _ (L _ (OpApp _ a1 op1 a2))))) <- splitInfix x
             , isDol op1
             , isVar a1 || isApp a1 || isPar a1, not $ isAtom a2
             , varToStr a1 /= "select" -- special case for esqueleto, see #224
-            , let y = noLoc $ HsApp noExtField a1 (noLoc (HsPar noExtField a2))
-            , let r = Replace Expr (toSS e) [("a", toSS a1), ("b", toSS a2)] "a (b)" ]
+            , let y = noLocA $ HsApp noExtField a1 (nlHsPar a2)
+            , let r = Replace Expr (toSSA e) [("a", toSSA a1), ("b", toSSA a2)] "a (b)" ]
           ++  -- Special case of (v1 . v2) <$> v3
-          [ (suggest "Redundant bracket" x y [r]){ideaSpan = locPar}
+          [ (suggest "Redundant bracket" (reLoc x) (reLoc y) [r]){ideaSpan = locA locPar}
           | L _ (OpApp _ (L locPar (HsPar _ o1@(L locNoPar (OpApp _ _ (isDot -> True) _)))) o2 v3) <- [x], varToStr o2 == "<$>"
-          , let y = noLoc (OpApp noExtField o1 o2 v3) :: LHsExpr GhcPs
-          , let r = Replace Expr (toRefactSrcSpan locPar) [("a", toRefactSrcSpan locNoPar)] "a"]
+          , let y = noLocA (OpApp noExtField o1 o2 v3) :: LHsExpr GhcPs
+          , let r = Replace Expr (toRefactSrcSpan (locA locPar)) [("a", toRefactSrcSpan (locA locNoPar))] "a"]
           ++
-          [ suggest "Redundant section" x y [r]
+          [ suggest "Redundant section" (reLoc x) (reLoc y) [r]
           | L _ (HsApp _ (L _ (HsPar _ (L _ (SectionL _ a b)))) c) <- [x]
           -- , error $ show (unsafePrettyPrint a, gshow b, unsafePrettyPrint c)
-          , let y = noLoc $ OpApp noExtField a b c :: LHsExpr GhcPs
-          , let r = Replace Expr (toSS x) [("x", toSS a), ("op", toSS b), ("y", toSS c)] "x op y"]
+          , let y = noLocA $ OpApp noExtField a b c :: LHsExpr GhcPs
+          , let r = Replace Expr (toSSA x) [("x", toSSA a), ("op", toSSA b), ("y", toSSA c)] "x op y"]
 
 splitInfix :: LHsExpr GhcPs -> [(LHsExpr GhcPs -> LHsExpr GhcPs, LHsExpr GhcPs)]
 splitInfix (L l (OpApp _ lhs op rhs)) =
diff --git a/src/Hint/Comment.hs b/src/Hint/Comment.hs
--- a/src/Hint/Comment.hs
+++ b/src/Hint/Comment.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 
 {-
 <TEST>
@@ -18,9 +19,10 @@
 import Data.Char
 import Data.List.Extra
 import Refact.Types(Refactoring(ModifyComment))
-import SrcLoc
-import ApiAnnotation
+import GHC.Types.SrcLoc
+import GHC.Parser.Annotation
 import GHC.Util
+import GHC.Data.Strict qualified
 
 directives :: [String]
 directives = words $
@@ -31,7 +33,7 @@
 commentHint :: ModuHint
 commentHint _ m = concatMap chk (ghcComments m)
     where
-        chk :: Located AnnotationComment -> [Idea]
+        chk :: LEpaComment -> [Idea]
         chk comm
           | isMultiline, "#" `isSuffixOf` s && not ("#" `isPrefixOf` s) = [grab "Fix pragma markup" comm $ '#':s]
           | isMultiline, name `elem` directives = [grab "Use pragma syntax" comm $ "# " ++ trim s ++ " #"]
@@ -41,9 +43,11 @@
                  name = takeWhile (\x -> isAlphaNum x || x == '_') $ trimStart s
         chk _ = []
 
-        grab :: String -> Located AnnotationComment -> String -> Idea
+        grab :: String -> LEpaComment -> String -> Idea
         grab msg o@(L pos _) s2 =
-          let s1 = commentText o in
-          rawIdea Suggestion msg pos (f s1) (Just $ f s2) [] refact
+          let s1 = commentText o
+              loc = RealSrcSpan (epaLocationRealSrcSpan pos) GHC.Data.Strict.Nothing
+          in
+          rawIdea Suggestion msg loc (f s1) (Just $ f s2) [] (refact loc)
             where f s = if isCommentMultiline o then "{-" ++ s ++ "-}" else "--" ++ s
-                  refact = [ModifyComment (toRefactSrcSpan pos) (f s2)]
+                  refact loc = [ModifyComment (toRefactSrcSpan loc) (f s2)]
diff --git a/src/Hint/Duplicate.hs b/src/Hint/Duplicate.hs
--- a/src/Hint/Duplicate.hs
+++ b/src/Hint/Duplicate.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
 {-# LANGUAGE FlexibleContexts #-}
 
@@ -30,13 +31,12 @@
 import Data.Maybe
 import Data.Tuple.Extra
 import Data.List hiding (find)
-import qualified Data.List.NonEmpty as NE
-import qualified Data.Map as Map
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as Map
 
-import SrcLoc
+import GHC.Types.SrcLoc
 import GHC.Hs
-import Outputable
-import Bag
+import GHC.Utils.Outputable
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
@@ -53,14 +53,14 @@
    dupes [ (m, d, y)
          | (m, d, x) <- ds
          , HsValBinds _ (ValBinds _ b _ ) :: HsLocalBinds GhcPs <- universeBi x
-         , let y = bagToList b
+         , let y = b
          ]
     where
       ds = [(modName m, fromMaybe "" (declName d), unLoc d)
-           | ModuleEx m _ <- map snd ms
+           | ModuleEx m <- map snd ms
            , d <- hsmodDecls (unLoc m)]
 
-dupes :: (Outputable e, Data e) => [(String, String, [Located e])] -> [Idea]
+dupes :: (Outputable e, Data e) => [(String, String, [LocatedA e])] -> [Idea]
 dupes ys =
     [(rawIdeaN
         (if length xs >= 5 then Hint.Type.Warning else Suggestion)
@@ -72,7 +72,7 @@
     | ((m1, d1, SrcSpanD p1), (m2, d2, SrcSpanD p2), xs) <- duplicateOrdered 3 $ map f ys]
     where
       f (m, d, xs) =
-        [((m, d, SrcSpanD (getLoc x)), extendInstances (stripLocs x)) | x <- xs]
+        [((m, d, SrcSpanD (locA (getLoc x))), extendInstances (stripLocs x)) | x <- xs]
 
 ---------------------------------------------------------------------
 -- DUPLICATE FINDING
diff --git a/src/Hint/Export.hs b/src/Hint/Export.hs
--- a/src/Hint/Export.hs
+++ b/src/Hint/Export.hs
@@ -16,15 +16,14 @@
 import Hint.Type(ModuHint, ModuleEx(..),ideaNote,ignore,Note(..))
 
 import GHC.Hs
-import Module
-import SrcLoc
-import OccName
-import RdrName
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name.Reader
 
 exportHint :: ModuHint
-exportHint _ (ModuleEx (L s m@HsModule {hsmodName = Just name, hsmodExports = exports}) _)
+exportHint _ (ModuleEx (L s m@HsModule {hsmodName = Just name, hsmodExports = exports}) )
   | Nothing <- exports =
-      let r = o{ hsmodExports = Just (noLoc [noLoc (IEModuleContents noExtField name)] )} in
+      let r = o{ hsmodExports = Just (noLocA [noLocA (IEModuleContents (Nothing, noAnn) name)] )} in
       [(ignore "Use module export list" (L s o) (noLoc r) []){ideaNote = [Note "an explicit list is usually better"]}]
   | Just (L _ xs) <- exports
   , mods <- [x | x <- xs, isMod x]
@@ -33,11 +32,11 @@
   , exports' <- [x | x <- xs, not (matchesModName modName x)]
   , modName `elem` names =
       let dots = mkRdrUnqual (mkVarOcc " ... ")
-          r = o{ hsmodExports = Just (noLoc (noLoc (IEVar noExtField (noLoc (IEName (noLoc dots)))) : exports') )}
+          r = o{ hsmodExports = Just (noLocA (noLocA (IEVar Nothing (noLocA (IEName noExtField (noLocA dots))) Nothing) : exports') )}
       in
         [ignore "Use explicit module export list" (L s o) (noLoc r) []]
       where
-          o = m{hsmodImports=[], hsmodDecls=[], hsmodDeprecMessage=Nothing, hsmodHaddockModHeader=Nothing }
+          o = m{hsmodImports=[], hsmodDecls=[] }
           isMod (L _ (IEModuleContents _ _)) = True
           isMod _ = False
 
diff --git a/src/Hint/Extensions.hs b/src/Hint/Extensions.hs
--- a/src/Hint/Extensions.hs
+++ b/src/Hint/Extensions.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE LambdaCase, NamedFieldPuns #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase, NamedFieldPuns, ScopedTypeVariables #-}
 
 {-
     Suggest removal of unnecessary extensions
@@ -15,7 +16,9 @@
 {-# LANGUAGE TemplateHaskell #-} \
 $(deriveNewtypes typeInfo)
 {-# LANGUAGE TemplateHaskell #-} \
-main = foo ''Bar
+main = foo ''Bar --
+{-# LANGUAGE QuasiQuotes, TemplateHaskell #-} \
+f x = x + [e| x + 1 |] + [foo| x + 1 |] -- {-# LANGUAGE QuasiQuotes #-}
 {-# LANGUAGE PatternGuards #-} \
 test = case x of _ | y <- z -> w
 {-# LANGUAGE TemplateHaskell,EmptyDataDecls #-} \
@@ -95,6 +98,8 @@
 deriving instance Show Bar -- {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving #-} \
 newtype Micro = Micro Int deriving Generic -- {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveGeneric, TypeFamilies #-} \
+data family Bar a; data instance Bar Foo = Foo deriving Generic
 {-# LANGUAGE GeneralizedNewtypeDeriving #-} \
 instance Class Int where {newtype MyIO a = MyIO a deriving NewClass}
 {-# LANGUAGE UnboxedTuples #-} \
@@ -120,6 +125,8 @@
 foo = id --
 {-# LANGUAGE TypeApplications #-} \
 foo = id @Int
+{-# LANGUAGE TypeApplications #-} \
+x :: Typeable b => TypeRep @Bool b
 {-# LANGUAGE LambdaCase #-} \
 foo = id --
 {-# LANGUAGE LambdaCase #-} \
@@ -228,13 +235,34 @@
 data T m a = MkT (m a) (T Maybe (m a))
 {-# LANGUAGE NoMonomorphismRestriction, NamedFieldPuns #-} \
 main = 1 -- @Note Extension NamedFieldPuns is not used
+{-# LANGUAGE FunctionalDependencies #-} \
+class HasField x r a | x r -> a
+{-# LANGUAGE OverloadedRecordDot #-} \
+f x = x.foo
+{-# LANGUAGE OverloadedRecordDot #-} \
+f x = x . foo -- @NoRefactor: refactor requires GHC >= 9.2.1
+{-# LANGUAGE OverloadedRecordDot #-} \
+f = (.foo)
+{-# LANGUAGE OverloadedRecordDot #-} \
+f = (. foo) -- @NoRefactor: refactor requires GHC >= 9.2.1
+{-# LANGUAGE TemplateHaskellQuotes #-} \
+foo = [|| x ||]
+{-# LANGUAGE TemplateHaskell #-} \
+foo = $bar
+{-# LANGUAGE TemplateHaskell #-} \
+foo = $$typedExpressionSplice
+{-# LANGUAGE TypeData # -} \
+type data Nat = Zero | Succ Nat  -- @NoRefactor: refactor requires GHC >= 9.6.1
+{-# LANGUAGE TypeData #-} \
+data T = MkT -- @NoRefactor: refactor requires GHC >= 9.6.1
 </TEST>
 -}
 
 
+
 module Hint.Extensions(extensionsHint) where
 
-import Hint.Type(ModuHint,rawIdea,Severity(Warning),Note(..),toSS,ghcAnnotations,ghcModule)
+import Hint.Type(ModuHint,rawIdea,Severity(Warning),Note(..),toSSAnc,ghcModule,modComments,firstDeclComments)
 import Extension
 
 import Data.Generics.Uniplate.DataOnly
@@ -243,22 +271,25 @@
 import Data.List.Extra
 import Data.Data
 import Refact.Types
-import qualified Data.Set as Set
-import qualified Data.Map as Map
+import Data.Set qualified as Set
+import Data.Map qualified as Map
 
-import SrcLoc
+import GHC.Data.FastString
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
 import GHC.Hs
-import BasicTypes
-import Class
-import RdrName
-import ForeignCall
+import GHC.Types.Basic
+import GHC.Types.Name.Reader
+import GHC.Types.ForeignCall
+import GHC.Data.Strict qualified
+import GHC.Types.PkgQual
 
 import GHC.Util
 import GHC.LanguageExtensions.Type
 
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
-import Language.Haskell.GhclibParserEx.GHC.Hs.Types
+import Language.Haskell.GhclibParserEx.GHC.Hs.Type
 import Language.Haskell.GhclibParserEx.GHC.Hs.Decls
 import Language.Haskell.GhclibParserEx.GHC.Hs.Binds
 import Language.Haskell.GhclibParserEx.GHC.Hs.ImpExp
@@ -268,15 +299,22 @@
 
 extensionsHint :: ModuHint
 extensionsHint _ x =
-    [ rawIdea Hint.Type.Warning "Unused LANGUAGE pragma"
-        sl
-        (comment (mkLanguagePragmas sl exts))
+    [
+        rawIdea Hint.Type.Warning "Unused LANGUAGE pragma"
+        (RealSrcSpan (epaLocationRealSrcSpan sl) GHC.Data.Strict.Nothing)
+        (comment_ (mkLanguagePragmas sl exts))
         (Just newPragma)
         ( [RequiresExtension (show gone) | (_, Just x) <- before \\ after, gone <- Map.findWithDefault [] x disappear] ++
             [ Note $ "Extension " ++ s ++ " is " ++ reason x
             | (s, Just x) <- explainedRemovals])
-        [ModifyComment (toSS (mkLanguagePragmas sl exts)) newPragma]
-    | (L sl _,  exts) <- languagePragmas $ pragmas (ghcAnnotations x)
+        [ModifyComment (toSSAnc (mkLanguagePragmas sl exts)) newPragma]
+    | (L sl _,  exts) <-
+      -- Comments appearing without an empty line before the first
+      -- declaration in a module are now associated with the
+      -- declaration not the module so to be safe, look also at
+      -- `firstDeclComments x`
+      -- (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
+      languagePragmas $ pragmas (modComments x) ++ pragmas (firstDeclComments x)
     , let before = [(x, readExtension x) | x <- exts]
     , let after = filter (maybe True (`Set.member` keep) . snd) before
     , before /= after
@@ -284,22 +322,35 @@
             | null after && not (any (`Map.member` implied) $ mapMaybe snd before) = []
             | otherwise = before \\ after
     , let newPragma =
-            if null after then "" else comment (mkLanguagePragmas sl $ map fst after)
+            if null after then "" else comment_ (mkLanguagePragmas sl $ map fst after)
     ]
   where
     usedTH :: Bool
-    usedTH = used TemplateHaskell (ghcModule x) || used QuasiQuotes (ghcModule x)
+    usedTH = used TemplateHaskell (ghcModule x)
+               || used TemplateHaskellQuotes (ghcModule x)
+               || used QuasiQuotes (ghcModule x)
       -- If TH or QuasiQuotes is on, can use all other extensions
       -- programmatically.
 
     -- All the extensions defined to be used.
     extensions :: Set.Set Extension
-    extensions = Set.fromList $ mapMaybe readExtension $
-        concatMap snd $ languagePragmas (pragmas (ghcAnnotations x))
+    extensions = Set.fromList $
+      concatMap
+      (mapMaybe readExtension . snd)
+      (languagePragmas
+        (pragmas (modComments x) ++ pragmas (firstDeclComments x)))
+      -- Comments appearing without an empty line before the first
+      -- declaration in a module are now associated with the
+      -- declaration not the module so to be safe, look also at
+      -- `firstDeclComments x`
+      -- (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
 
     -- Those extensions we detect to be useful.
     useful :: Set.Set Extension
-    useful = if usedTH then extensions else Set.filter (`usedExt` ghcModule x) extensions
+    useful =
+      if usedTH
+        then Set.filter (\case TemplateHaskell -> usedExt TemplateHaskell (ghcModule x); _ -> True) extensions
+        else Set.filter (`usedExt` ghcModule x) extensions
     -- Those extensions which are useful, but implied by other useful
     -- extensions.
     implied :: Map.Map Extension Extension
@@ -351,36 +402,44 @@
 usedExt x = used x
 
 used :: Extension -> Located (HsModule GhcPs) -> Bool
+
 used RecursiveDo = hasS isMDo ||^ hasS isRecStmt
 used ParallelListComp = hasS isParComp
-used FunctionalDependencies = hasT (un :: FunDep (Located RdrName))
+used FunctionalDependencies = hasT (un :: FunDep GhcPs)
 used ImplicitParams = hasT (un :: HsIPName)
-used TypeApplications = hasS isTypeApp
+
+used TypeApplications = hasS isTypeApp ||^ hasS isKindTyApp
+
 used EmptyDataDecls = hasS f
   where
     f :: HsDataDefn GhcPs -> Bool
-    f (HsDataDefn _ _ _ _ _ [] _) = True
+    f (HsDataDefn _ _ _ _ (DataTypeCons _ []) _) = True
     f _ = False
+used TypeData  = hasS f
+  where
+    f :: HsDataDefn GhcPs -> Bool
+    f (HsDataDefn _ _ _ _ (DataTypeCons True _) _) = True
+    f _ = False
 used EmptyCase = hasS f
   where
     f :: HsExpr GhcPs -> Bool
-    f (HsCase _ _ (MG _ (L _ []) _)) = True
-    f (HsLamCase _ (MG _ (L _ []) _)) = True
+    f (HsCase _ _ (MG _ (L _ []))) = True
+    f (HsLam _ LamCase (MG _ (L _ []))) = True
     f _ = False
 used KindSignatures = hasT (un :: HsKind GhcPs)
 used BangPatterns = hasS isPBangPat ||^ hasS isStrictMatch
-used TemplateHaskell = hasT2' (un :: (HsBracket GhcPs, HsSplice GhcPs)) ||^ hasS f ||^ hasS isSpliceDecl
+used TemplateHaskell = hasS (not . isQuasiQuoteSplice) ||^ hasS isTypedSplice
+used TemplateHaskellQuotes = hasS f
   where
-    f :: HsBracket GhcPs -> Bool
-    f VarBr{} = True
-    f TypBr{} = True
+    f :: HsExpr GhcPs -> Bool
+    f HsTypedBracket{} = True
+    f HsUntypedBracket{} = True
     f _ = False
 used ForeignFunctionInterface = hasT (un :: CCallConv)
 used PatternGuards = hasS f
   where
     f :: GRHS GhcPs (LHsExpr GhcPs) -> Bool
     f (GRHS _ xs _) = g xs
-    f _ = False -- Extension constructor
     g :: [GuardLStmt GhcPs] -> Bool
     g [] = False
     g [L _ BodyStmt{}] = False
@@ -403,7 +462,7 @@
     isOp (L _ name) = isSymbolRdrName name
 
 used RecordWildCards = hasS hasFieldsDotDot ||^ hasS hasPFieldsDotDot
-used RecordPuns = hasS isPFieldPun ||^ hasS isFieldPun ||^ hasS isFieldPunUpdate
+used NamedFieldPuns = hasS isPFieldPun ||^ hasS isFieldPun ||^ hasS isFieldPunUpdate
 used UnboxedTuples = hasS isUnboxedTuple ||^ hasS (== Unboxed) ||^ hasS isDeriving
   where
       -- detect if there are deriving declarations or data ... deriving stuff
@@ -414,9 +473,9 @@
 used PackageImports = hasS f
   where
       f :: ImportDecl GhcPs -> Bool
-      f ImportDecl{ideclPkgQual=Just _} = True
+      f ImportDecl{ideclPkgQual=RawPkgQual _} = True
       f _ = False
-used QuasiQuotes = hasS isQuasiQuote ||^ hasS isTyQuasiQuote
+used QuasiQuotes = hasS isQuasiQuoteExpr ||^ hasS isTyQuasiQuote
 used ViewPatterns = hasS isPViewPat
 used InstanceSigs = hasS f
   where
@@ -434,8 +493,8 @@
 used NumericUnderscores = hasS f
   where
     f :: OverLitVal -> Bool
-    f (HsIntegral (IL (SourceText t) _ _)) = '_' `elem` t
-    f (HsFractional (FL (SourceText t) _ _)) = '_' `elem` t
+    f (HsIntegral (IL (SourceText t) _ _)) = '_' `elem` unpackFS t
+    f (HsFractional (FL (SourceText t) _ _ _ _)) = '_' `elem` unpackFS t
     f _ = False
 
 used LambdaCase = hasS isLCase
@@ -453,11 +512,7 @@
     isListPat ListPat{} = True
     isListPat _ = False
 
-used OverloadedLabels = hasS isLabel
-  where
-    isLabel :: HsExpr GhcPs -> Bool
-    isLabel HsOverLabel{} = True
-    isLabel _ = False
+used OverloadedLabels = hasS isOverLabel
 
 used Arrows = hasS isProc
 used TransformListComp = hasS isTransStmt
@@ -468,6 +523,7 @@
 used PatternSynonyms = hasS isPatSynBind ||^ hasS isPatSynIE
 used ImportQualifiedPost = hasS (== QualifiedPost)
 used StandaloneKindSignatures = hasT (un :: StandaloneKindSig GhcPs)
+used OverloadedRecordDot = hasT (un :: DotFieldOcc GhcPs)
 
 used _= const True
 
@@ -490,10 +546,10 @@
 
 addDerives :: Maybe NewOrData -> Maybe (DerivStrategy GhcPs) -> [String] -> Derives
 addDerives _ (Just s) xs = case s of
-    StockStrategy -> mempty{derivesStock' = xs}
-    AnyclassStrategy -> mempty{derivesAnyclass = xs}
-    NewtypeStrategy -> mempty{derivesNewtype' = xs}
-    ViaStrategy{} -> mempty
+    StockStrategy {} -> mempty{derivesStock' = xs}
+    AnyclassStrategy {} -> mempty{derivesAnyclass = xs}
+    NewtypeStrategy {} -> mempty{derivesNewtype' = xs}
+    ViaStrategy {} -> mempty
 addDerives nt _ xs = mempty
     {derivesStock' = stock
     ,derivesAnyclass = other
@@ -503,20 +559,23 @@
 derives :: Located (HsModule GhcPs) -> Derives
 derives (L _ m) =  mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m)
   where
-    idecl :: Located (DataFamInstDecl GhcPs) -> Derives
-    idecl (L _ (DataFamInstDecl (HsIB _ FamEqn {feqn_rhs=HsDataDefn {dd_ND=dn, dd_derivs=(L _ ds)}}))) = g dn ds
-    idecl _ = mempty
+    idecl :: DataFamInstDecl GhcPs -> Derives
+    idecl (DataFamInstDecl FamEqn {feqn_rhs=HsDataDefn {dd_cons=data_defn_cons, dd_derivs=ds}}) = g (dataDefnConsNewOrData data_defn_cons) ds
 
     decl :: LHsDecl GhcPs -> Derives
-    decl (L _ (TyClD _ (DataDecl _ _ _ _ HsDataDefn {dd_ND=dn, dd_derivs=(L _ ds)}))) = g dn ds -- Data declaration.
+    decl (L _ (TyClD _ (DataDecl _ _ _ _ HsDataDefn {dd_cons=data_defn_cons, dd_derivs=ds}))) = g (dataDefnConsNewOrData data_defn_cons) ds -- Data declaration.
     decl (L _ (DerivD _ (DerivDecl _ (HsWC _ sig) strategy _))) = addDerives Nothing (fmap unLoc strategy) [derivedToStr sig] -- A deriving declaration.
     decl _ = mempty
 
     g :: NewOrData -> [LHsDerivingClause GhcPs] -> Derives
-    g dn ds = mconcat [addDerives (Just dn) (fmap unLoc strategy) $ map derivedToStr tys | L _ (HsDerivingClause _ strategy (L _ tys)) <- ds]
+    g dn ds = mconcat [addDerives (Just dn) (fmap unLoc strategy) $ map derivedToStr tys | (strategy, tys) <- stys]
+      where
+        stys =
+          [(strategy, [ty]) | L _ (HsDerivingClause _ strategy (L _ (DctSingle _ ty))) <- ds] ++
+          [(strategy, tys ) | L _ (HsDerivingClause _ strategy (L _ (DctMulti _ tys))) <- ds]
 
     derivedToStr :: LHsSigType GhcPs -> String
-    derivedToStr (HsIB _ t) = ih t
+    derivedToStr (L _ (HsSig _ _ t)) = ih t
       where
         ih :: LHsType GhcPs -> String
         ih (L _ (HsQualTy _ _ a)) = ih a
@@ -524,12 +583,10 @@
         ih (L _ (HsAppTy _ a _)) = ih a
         ih (L _ (HsTyVar _ _ a)) = unsafePrettyPrint $ unqual a
         ih (L _ a) = unsafePrettyPrint a -- I don't anticipate this case is called.
-    derivedToStr _ = "" -- new ctor
 
 un = undefined
 
 hasT t x = not $ null (universeBi x `asTypeOf` [t])
-hasT2' ~(t1,t2) = hasT t1 ||^ hasT t2
 
 hasS :: (Data x, Data a) => (a -> Bool) -> x -> Bool
 hasS test = any test . universeBi
diff --git a/src/Hint/Fixities.hs b/src/Hint/Fixities.hs
--- a/src/Hint/Fixities.hs
+++ b/src/Hint/Fixities.hs
@@ -16,7 +16,7 @@
 
 module Hint.Fixities(fixitiesHint) where
 
-import Hint.Type(DeclHint,Idea(..),rawIdea,toSS)
+import Hint.Type(DeclHint,Idea(..),rawIdea,toSSA)
 import Config.Type
 import Control.Monad
 import Data.List.Extra
@@ -24,21 +24,21 @@
 import Data.Generics.Uniplate.DataOnly
 import Refact.Types
 
-import BasicTypes (compareFixity)
+import GHC.Types.Fixity(compareFixity)
 import Fixity
 import GHC.Hs
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
-import OccName
-import SrcLoc
-import RdrName
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
 
 fixitiesHint :: [Setting] -> DeclHint
 fixitiesHint settings _ _ x =
   concatMap (infixBracket fixities) (childrenBi x :: [LHsExpr GhcPs])
    where
      fixities = foldMap getFixity settings `mappend` fromList (toFixity <$> defaultFixities)
-     getFixity (Infix x) = uncurry singleton (toFixity x)
+     getFixity (Infix x) = uncurry Data.Map.singleton (toFixity x)
      getFixity _ = mempty
 
 infixBracket :: Map String Fixity -> LHsExpr GhcPs -> [Idea]
@@ -51,8 +51,8 @@
       Just x <- [remParen v]
       guard $ redundantInfixBracket fixities i o x
       pure $
-        rawIdea Ignore msg (getLoc v) (unsafePrettyPrint o)
-        (Just (unsafePrettyPrint (gen x))) [] [Replace (findType v) (toSS v) [("x", toSS x)] "x"]
+        rawIdea Ignore msg (locA (getLoc v)) (unsafePrettyPrint o)
+        (Just (unsafePrettyPrint (gen x))) [] [Replace (findType v) (toSSA v) [("x", toSSA x)] "x"]
 
 redundantInfixBracket :: Map String Fixity -> Int -> LHsExpr GhcPs -> LHsExpr GhcPs -> Bool
 redundantInfixBracket fixities i parent child
@@ -73,7 +73,6 @@
 needParenAsChild HsLet{} = True
 needParenAsChild HsDo{} = True
 needParenAsChild HsLam{} = True
-needParenAsChild HsLamCase{} = True
 needParenAsChild HsCase{} = True
 needParenAsChild HsIf{} = True
 needParenAsChild _ = False
diff --git a/src/Hint/Import.hs b/src/Hint/Import.hs
--- a/src/Hint/Import.hs
+++ b/src/Hint/Import.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE LambdaCase, PatternGuards, RecordWildCards #-}
 {-
     Reduce the number of import declarations.
@@ -37,9 +38,9 @@
 
 module Hint.Import(importHint) where
 
-import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),suggest,toSS,rawIdea)
+import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),suggest,toSSA,rawIdea)
 import Refact.Types hiding (ModuleName)
-import qualified Refact.Types as R
+import Refact.Types qualified as R
 import Data.Tuple.Extra
 import Data.List.Extra
 import Data.Generics.Uniplate.DataOnly
@@ -47,30 +48,36 @@
 import Control.Applicative
 import Prelude
 
-import FastString
-import BasicTypes
+import GHC.Data.FastString
+import GHC.Types.SourceText
 import GHC.Hs
-import SrcLoc
+import GHC.Types.SrcLoc
+import GHC.Types.PkgQual
 
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
+rawPkgQualToMaybe :: RawPkgQual -> Maybe StringLiteral
+rawPkgQualToMaybe x =
+  case x of
+    NoRawPkgQual -> Nothing
+    RawPkgQual lit -> Just lit
 
 importHint :: ModuHint
 importHint _ ModuleEx {ghcModule=L _ HsModule{hsmodImports=ms}} =
   -- Ideas for combining multiple imports.
   concatMap (reduceImports . snd) (
     groupSort [((n, pkg), i) | i <- ms
-              , not $ ideclSource (unLoc i)
+              , ideclSource (unLoc i) == NotBoot
               , let i' = unLoc i
               , let n = unLoc $ ideclName i'
-              , let pkg  = unpackFS . sl_fs <$> ideclPkgQual i']) ++
+              , let pkg  = unpackFS . sl_fs <$> rawPkgQualToMaybe (ideclPkgQual i')]) ++
   -- Ideas for removing redundant 'as' clauses.
   concatMap stripRedundantAlias ms
 
 reduceImports :: [LImportDecl GhcPs] -> [Idea]
 reduceImports [] = []
 reduceImports ms@(m:_) =
-  [rawIdea Hint.Type.Warning "Use fewer imports" (getLoc m) (f ms) (Just $ f x) [] rs
+  [rawIdea Hint.Type.Warning "Use fewer imports" (locA (getLoc m)) (f ms) (Just $ f x) [] rs
   | Just (x, rs) <- [simplify ms]]
   where f = unlines . map unsafePrettyPrint
 
@@ -96,29 +103,29 @@
         -> Maybe (LImportDecl GhcPs, [Refactoring R.SrcSpan])
 combine x@(L loc x') y@(L _ y')
   -- Both (un/)qualified, common 'as', same names : Delete the second.
-  | qual, as, specs = Just (x, [Delete Import (toSS y)])
+  | qual, as, specs = Just (x, [Delete Import (toSSA y)])
     -- Both (un/)qualified, common 'as', different names : Merge the
     -- second into the first and delete it.
   | qual, as
-  , Just (False, xs) <- ideclHiding x'
-  , Just (False, ys) <- ideclHiding y' =
-      let newImp = L loc x'{ideclHiding = Just (False, noLoc (unLoc xs ++ unLoc ys))}
-      in Just (newImp, [Replace Import (toSS x) [] (unsafePrettyPrint (unLoc newImp))
-                       , Delete Import (toSS y)])
+  , Just (False, xs) <- first (== EverythingBut) <$> ideclImportList x'
+  , Just (False, ys) <- first (== EverythingBut) <$> ideclImportList y' =
+      let newImp = L loc x'{ideclImportList = Just (Exactly, noLocA (unLoc xs ++ unLoc ys))}
+      in Just (newImp, [Replace Import (toSSA x) [] (unsafePrettyPrint (unLoc newImp))
+                       , Delete Import (toSSA y)])
   -- Both (un/qualified), common 'as', one has names the other doesn't
   -- : Delete the one with names.
-  | qual, as, isNothing (ideclHiding x') || isNothing (ideclHiding y') =
-       let (newImp, toDelete) = if isNothing (ideclHiding x') then (x, y) else (y, x)
-       in Just (newImp, [Delete Import (toSS toDelete)])
+  | qual, as, isNothing (ideclImportList x') || isNothing (ideclImportList y') =
+       let (newImp, toDelete) = if isNothing (ideclImportList x') then (x, y) else (y, x)
+       in Just (newImp, [Delete Import (toSSA toDelete)])
   -- Both unqualified, same names, one (and only one) has an 'as'
   -- clause : Delete the one without an 'as'.
   | ideclQualified x' == NotQualified, qual, specs, length ass == 1 =
        let (newImp, toDelete) = if isJust (ideclAs x') then (x, y) else (y, x)
-       in Just (newImp, [Delete Import (toSS toDelete)])
+       in Just (newImp, [Delete Import (toSSA toDelete)])
   -- No hints.
   | otherwise = Nothing
     where
-        eqMaybe:: Eq a => Maybe (Located a) -> Maybe (Located a) -> Bool
+        eqMaybe:: Eq a => Maybe (LocatedA a) -> Maybe (LocatedA a) -> Bool
         eqMaybe (Just x) (Just y) = x `eqLocated` y
         eqMaybe Nothing Nothing = True
         eqMaybe _ _ = False
@@ -126,12 +133,12 @@
         qual = ideclQualified x' == ideclQualified y'
         as = ideclAs x' `eqMaybe` ideclAs y'
         ass = mapMaybe ideclAs [x', y']
-        specs = transformBi (const noSrcSpan) (ideclHiding x') ==
-                    transformBi (const noSrcSpan) (ideclHiding y')
+        specs = transformBi (const noSrcSpan) (ideclImportList x') ==
+                    transformBi (const noSrcSpan) (ideclImportList y')
 
 stripRedundantAlias :: LImportDecl GhcPs -> [Idea]
-stripRedundantAlias x@(L loc i@ImportDecl {..})
+stripRedundantAlias x@(L _ i@ImportDecl {..})
   -- Suggest 'import M as M' be just 'import M'.
   | Just (unLoc ideclName) == fmap unLoc ideclAs =
-      [suggest "Redundant as" x (cL loc i{ideclAs=Nothing} :: LImportDecl GhcPs) [RemoveAsKeyword (toSS x)]]
+      [suggest "Redundant as" (reLoc x) (noLoc i{ideclAs=Nothing} :: Located (ImportDecl GhcPs)) [RemoveAsKeyword (toSSA x)]]
 stripRedundantAlias _ = []
diff --git a/src/Hint/Lambda.hs b/src/Hint/Lambda.hs
--- a/src/Hint/Lambda.hs
+++ b/src/Hint/Lambda.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE LambdaCase, PatternGuards, ViewPatterns #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase, PatternGuards, TupleSections, ViewPatterns #-}
 
 {-
     Concept:
@@ -32,10 +33,11 @@
 fun x y z = f x x y z -- fun x = f x x
 fun x y z = f g z -- fun x y = f g
 fun x = f . g $ x -- fun = f . g
+fun a b = f a b c where g x y = h x y -- g = h
+fun a b = let g x y = h x y in f a b c -- g = h
 f = foo (\y -> g x . h $ y) -- g x . h
 f = foo (\y -> g x . h $ y) -- @Message Avoid lambda
 f = foo ((*) x) -- (x *)
-f = foo ((Prelude.*) x) -- (x Prelude.*)
 f = (*) x
 f = foo (flip op x) -- (`op` x)
 f = foo (flip op x) -- @Message Use section
@@ -85,6 +87,7 @@
 yes = blah (\ x -> case x of A -> a; B -> b) -- \ case A -> a; B -> b
 yes = blah (\ x -> case x of A -> a; B -> b) -- @Note may require `{-# LANGUAGE LambdaCase #-}` adding to the top of the file
 no = blah (\ x -> case x of A -> a x; B -> b x)
+no = blah (\ x -> case x of A -> a x; x -> b x)
 foo = bar (\x -> case x of Y z | z > 0 -> z) -- \case Y z | z > 0 -> z
 yes = blah (\ x -> (y, x)) -- (y,)
 yes = blah (\ x -> (y, x, z+q)) -- (y, , z+q)
@@ -105,20 +108,22 @@
 
 module Hint.Lambda(lambdaHint) where
 
-import Hint.Type (DeclHint, Idea, Note(RequiresExtension), suggest, warn, toSS, suggestN, ideaNote, substVars, toRefactSrcSpan)
+import Hint.Type (DeclHint, Idea, Note(RequiresExtension), suggest, warn, toSS, toSSA, suggestN, ideaNote, substVars, toRefactSrcSpan)
 import Util
 import Data.List.Extra
 import Data.Set (Set)
-import qualified Data.Set as Set
-import Refact.Types hiding (RType(Match))
+import Data.Set qualified as Set
+import Refact.Types hiding (Match)
 import Data.Generics.Uniplate.DataOnly (universe, universeBi, transformBi)
 
-import BasicTypes
+import GHC.Types.Basic
+import GHC.Types.Fixity
 import GHC.Hs
-import OccName
-import RdrName
-import SrcLoc
-import Language.Haskell.GhclibParserEx.GHC.Hs.Expr (isTypeApp, isOpApp, isLambda, isQuasiQuote, isVar, isDol, strToVar)
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
+import Language.Haskell.GhclibParserEx.GHC.Hs.Expr (isTypeApp, isOpApp, isLambda, isQuasiQuoteExpr, isVar, isDol, strToVar)
+import Language.Haskell.GhclibParserEx.GHC.Hs.Pat (isWildPat)
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import GHC.Util.Brackets (isAtom)
@@ -129,52 +134,59 @@
 lambdaHint :: DeclHint
 lambdaHint _ _ x
     =  concatMap (uncurry lambdaExp) (universeParentBi x)
-    ++ concatMap lambdaDecl (universe x)
+    ++ concatMap (uncurry lambdaBind) binds
+  where
+    binds =
+        ( case x of
+            -- Turn a top-level HsBind under a ValD into an LHsBind.
+            -- Also, its refact type needs to be Decl.
+            L loc (ValD _ bind) -> ((L loc bind, Decl) :)
+            _ -> id
+        )
+            ((,Bind) <$> universeBi x)
 
-lambdaDecl :: LHsDecl GhcPs -> [Idea]
-lambdaDecl
-    o@(L _ (ValD _
-        origBind@FunBind {fun_id = funName@(L loc1 _), fun_matches =
-            MG {mg_alts =
-                L _ [L _ (Match _ ctxt@(FunRhs _ Prefix _) pats (GRHSs _ [L _ (GRHS _ [] origBody@(L loc2 _))] bind))]}}))
-    | L _ (EmptyLocalBinds _) <- bind
+lambdaBind :: LHsBind GhcPs -> RType -> [Idea]
+lambdaBind
+    o@(L _ origBind@FunBind {fun_id = funName@(L loc1 _), fun_matches =
+        MG {mg_alts =
+            L _ [L _ (Match _ ctxt@(FunRhs _ Prefix _ _) (L _ pats) (GRHSs _ [L _ (GRHS _ [] origBody@(L loc2 _))] bind))]}}) rtype
+    | EmptyLocalBinds _ <- bind
     , isLambda $ fromParen origBody
     , null (universeBi pats :: [HsExpr GhcPs])
     = let (newPats, newBody) = fromLambda . lambda pats $ origBody
           (sub, tpl) = mkSubtsAndTpl newPats newBody
-          gen :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs
+          gen :: [LPat GhcPs] -> LHsExpr GhcPs -> Located (HsDecl GhcPs)
           gen ps = uncurry reform . fromLambda . lambda ps
           refacts = case newBody of
               -- https://github.com/alanz/ghc-exactprint/issues/97
               L _ HsCase{} -> []
-              _ -> [Replace Decl (toSS o) sub tpl]
-       in [warn "Redundant lambda" o (gen pats origBody) refacts]
+              _ -> [Replace rtype (toSSA o) sub tpl]
+       in [warn "Redundant lambda" (reLoc o) (gen pats origBody) refacts]
 
     | let (newPats, newBody) = etaReduce pats origBody
     , length newPats < length pats, pvars (drop (length newPats) pats) `disjoint` varss bind
     = let (sub, tpl) = mkSubtsAndTpl newPats newBody
        in [warn "Eta reduce" (reform pats origBody) (reform newPats newBody)
-            [Replace Decl (toSS $ reform pats origBody) sub tpl]
+            [Replace rtype (toSS $ reform pats origBody) sub tpl]
           ]
-    where reform :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs
-          reform ps b = L (combineSrcSpans loc1 loc2) $ ValD noExtField $
-            origBind
-              {fun_matches = MG noExtField (noLoc [noLoc $ Match noExtField ctxt ps $ GRHSs noExtField [noLoc $ GRHS noExtField [] b] $ noLoc $ EmptyLocalBinds noExtField]) Generated}
+    where
+          reform :: [LPat GhcPs] -> LHsExpr GhcPs -> Located (HsDecl GhcPs)
+          reform ps b = L (combineSrcSpans (locA loc1) (locA loc2)) $ ValD noExtField $
+             origBind {fun_matches = MG (Generated OtherExpansion SkipPmc) (noLocA [noLocA $ Match noExtField ctxt (L noSpanAnchor ps) $ GRHSs emptyComments [noLocA $ GRHS noAnn [] b] $ EmptyLocalBinds noExtField])}
 
           mkSubtsAndTpl newPats newBody = (sub, tpl)
             where
               (origPats, vars) = mkOrigPats (Just (rdrNameStr funName)) newPats
-              sub = ("body", toSS newBody) : zip vars (map toSS newPats)
+              sub = ("body", toSSA newBody) : zip vars (map toSSA newPats)
               tpl = unsafePrettyPrint (reform origPats varBody)
 
-lambdaDecl _ = []
-
+lambdaBind _ _ = []
 
 etaReduce :: [LPat GhcPs] -> LHsExpr GhcPs -> ([LPat GhcPs], LHsExpr GhcPs)
 etaReduce (unsnoc -> Just (ps, view -> PVar_ p)) (L _ (HsApp _ x (view -> Var_ y)))
     | p == y
     , y `notElem` vars x
-    , not $ any isQuasiQuote $ universe x
+    , not $ any isQuasiQuoteExpr $ universe x
     = etaReduce ps x
 etaReduce ps (L loc (OpApp _ x (isDol -> True) y)) = etaReduce ps (L loc (HsApp noExtField x y))
 etaReduce ps x = (ps, x)
@@ -185,28 +197,29 @@
     , isAtom y
     , allowLeftSection $ occNameString f
     , not $ isTypeApp y
-    = [suggest "Use section" o to [r]]
+    = [suggest "Use section" (reLoc o) (reLoc to) [r]]
     where
         to :: LHsExpr GhcPs
-        to = noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField y oper
-        r = Replace Expr (toSS o) [("x", toSS y)] ("(x " ++ unsafePrettyPrint origf ++ ")")
+        to = nlHsPar $ noLocA $ SectionL noExtField y oper
+        r = Replace Expr (toSSA o) [("x", toSSA y)] ("(x " ++ unsafePrettyPrint origf ++ ")")
 
 lambdaExp _ o@(L _ (HsPar _ (view -> App2 (view -> Var_ "flip") origf@(view -> RdrName_ f) y)))
     | allowRightSection (rdrNameStr f), not $ "(" `isPrefixOf` rdrNameStr f
-    = [suggest "Use section" o to [r]]
+    = [suggest "Use section" (reLoc o) (reLoc to) [r]]
     where
         to :: LHsExpr GhcPs
-        to = noLoc $ HsPar noExtField $ noLoc $ SectionR noExtField origf y
+        to = nlHsPar $ noLocA $ SectionR noExtField origf y
         op = if isSymbolRdrName (unLoc f)
                then unsafePrettyPrint f
                else "`" ++ unsafePrettyPrint f ++ "`"
         var = if rdrNameStr f == "x" then "y" else "x"
-        r = Replace Expr (toSS o) [(var, toSS y)] ("(" ++ op ++ " " ++ var ++ ")")
-lambdaExp p o@(L _ HsLam{})
+        r = Replace Expr (toSSA o) [(var, toSSA y)] ("(" ++ op ++ " " ++ var ++ ")")
+
+lambdaExp p o@(L _ (HsLam _ LamSingle _))
     | not $ any isOpApp p
     , (res, refact) <- niceLambdaR [] o
     , not $ isLambda res
-    , not $ any isQuasiQuote $ universe res
+    , not $ any isQuasiQuoteExpr $ universe res
     , not $ "runST" `Set.member` Set.map occNameString (freeVars o)
     , let name = "Avoid lambda" ++ (if countRightSections res > countRightSections o then " using `infix`" else "")
     -- If the lambda's parent is an HsPar, and the result is also an HsPar, the span should include the parentheses.
@@ -216,7 +229,7 @@
                 | L _ HsPar{} <- res -> p
                 | L _ (HsVar _ (L _ name)) <- res, not (isSymbolRdrName name) -> p
               _ -> o
-    = [(if isVar res then warn else suggest) name from res (refact $ toSS from)]
+    = [(if isVar res then warn else suggest) name (reLoc from) (reLoc res) (refact $ toSSA from)]
     where
         countRightSections :: LHsExpr GhcPs -> Int
         countRightSections x = length [() | L _ (SectionR _ (view -> Var_ _) _) <- universe x]
@@ -225,11 +238,11 @@
     | isLambda (fromParen origBody)
     , null (universeBi origPats :: [HsExpr GhcPs]) -- TODO: I think this checks for view patterns only, so maybe be more explicit about that?
     , maybe True (not . isLambda) p =
-    [suggest "Collapse lambdas" o (lambda pats body) [Replace Expr (toSS o) subts template]]
+    [suggest "Collapse lambdas" (reLoc o) (reLoc (lambda pats body)) [Replace Expr (toSSA o) subts template]]
     where
       (pats, body) = fromLambda o
       (oPats, vars) = mkOrigPats Nothing pats
-      subts = ("body", toSS body) : zip vars (map toSS pats)
+      subts = ("body", toSSA body) : zip vars (map toSSA pats)
       template = unsafePrettyPrint (lambda oPats varBody)
 
 -- match a lambda with a variable pattern, with no guards and no where clauses
@@ -241,9 +254,8 @@
             | ([_x], ys) <- partition ((==Just x) . tupArgVar) args
             -- the other arguments must not have a nested x somewhere in them
             , Set.notMember x $ Set.map occNameString $ freeVars ys
-            -> [(suggestN "Use tuple-section" o $ noLoc $ ExplicitTuple noExtField (map removeX args) boxity)
+            -> [(suggestN "Use tuple-section" (reLoc o) $ noLoc $ ExplicitTuple noAnn (map removeX args) boxity)
                   {ideaNote = [RequiresExtension "TupleSections"]}]
-
         -- suggest @LambdaCase@/directly matching in a lambda instead of doing @\x -> case x of ...@
         HsCase _ (view -> Var_ x') matchGroup
             -- is the case being done on the variable from our original lambda?
@@ -256,49 +268,48 @@
                  -- we need to
                  --     * add brackets to the match, because matches in lambdas require them
                  --     * mark match as being in a lambda context so that it's printed properly
-                 oldMG@(MG _ (L _ [L _ oldmatch]) _)
+                 oldMG@(MG _ (L _ [L _ oldmatch]))
                    | all (\(L _ (GRHS _ stmts _)) -> null stmts) (grhssGRHSs (m_grhss oldmatch)) ->
-                     let patLocs = fmap getLoc (m_pats oldmatch)
-                         bodyLocs = concatMap (\case L _ (GRHS _ _ body) -> [getLoc body]; _ -> [])
+                     let patLocs = fmap (locA . getLoc) ((unLoc . m_pats) oldmatch)
+                         bodyLocs = concatMap (\case L _ (GRHS _ _ body) -> [locA (getLoc body)])
                                         $ grhssGRHSs (m_grhss oldmatch)
                          r | notNull patLocs && notNull bodyLocs =
                              let xloc = foldl1' combineSrcSpans patLocs
                                  yloc = foldl1' combineSrcSpans bodyLocs
-                              in [ Replace Expr (toSS o) [("x", toRefactSrcSpan xloc), ("y", toRefactSrcSpan yloc)]
+                              in [ Replace Expr (toSSA o) [("x", toRefactSrcSpan xloc), ("y", toRefactSrcSpan yloc)]
                                      ((if needParens then "\\(x)" else "\\x") ++ " -> y")
                                  ]
                            | otherwise = []
-                         needParens = any (patNeedsParens appPrec . unLoc) (m_pats oldmatch)
-                      in [ suggest "Use lambda" o
-                             ( noLoc $ HsLam noExtField oldMG
-                                 { mg_alts = noLoc
-                                     [ noLoc oldmatch
-                                         { m_pats = map mkParPat $ m_pats oldmatch
-                                         , m_ctxt = LambdaExpr
+                         needParens = any (patNeedsParens appPrec . unLoc) ((unLoc . m_pats) oldmatch)
+                      in [ suggest "Use lambda" (reLoc o)
+                             ( noLoc $ HsLam noAnn LamSingle oldMG
+                                 { mg_alts = noLocA
+                                     [ noLocA oldmatch
+                                         { m_pats = L noSpanAnchor (map mkParPat $ (unLoc . m_pats) oldmatch)
+                                         , m_ctxt = LamAlt LamSingle
                                          }
                                      ]
                                  }
-                               :: LHsExpr GhcPs
+                               :: Located (HsExpr GhcPs)
                              )
                              r
                          ]
 
                  -- otherwise we should use @LambdaCase@
-                 MG _ (L _ _) _ ->
-                     [(suggestN "Use lambda-case" o $ noLoc $ HsLamCase noExtField matchGroup)
+                 MG _ (L _ _) ->
+                     [(suggestN "Use lambda-case" (reLoc o) $ noLoc $ HsLam noAnn LamCase matchGroup)
                          {ideaNote=[RequiresExtension "LambdaCase"]}]
-                 _ -> []
         _ -> []
     where
         -- | Filter out tuple arguments, converting the @x@ (matched in the lambda) variable argument
         -- to a missing argument, so that we get the proper section.
-        removeX :: LHsTupArg GhcPs -> LHsTupArg GhcPs
-        removeX (L _ (Present _ (view -> Var_ x')))
-            | x == x' = noLoc $ Missing noExtField
+        removeX :: HsTupArg GhcPs -> HsTupArg GhcPs
+        removeX (Present _ (view -> Var_ x'))
+            | x == x' = Missing noAnn
         removeX y = y
         -- | Extract the name of an argument of a tuple if it's present and a variable.
-        tupArgVar :: LHsTupArg GhcPs -> Maybe String
-        tupArgVar (L _ (Present _ (view -> Var_ x))) = Just x
+        tupArgVar :: HsTupArg GhcPs -> Maybe String
+        tupArgVar (Present _ (view -> Var_ x)) = Just x
         tupArgVar _ = Nothing
 
 lambdaExp _ _ = []
@@ -336,10 +347,7 @@
            in (used, (True, p))
       | otherwise = (mempty, (False, p))
 
-    isWildPat :: LPat GhcPs -> Bool
-    isWildPat = \case (L _ (WildPat _)) -> True; _ -> False
-
     -- Replace the pattern with a variable pattern if the pattern doesn't contain wildcards.
     munge :: String -> (Bool, LPat GhcPs) -> LPat GhcPs
     munge _ (True, p) = p
-    munge ident (False, L ploc _) = L ploc (VarPat noExtField (L ploc $ mkRdrUnqual $ mkVarOcc ident))
+    munge ident (False, L ploc _) = L ploc (VarPat noExtField (noLocA $ mkRdrUnqual $ mkVarOcc ident))
diff --git a/src/Hint/List.hs b/src/Hint/List.hs
--- a/src/Hint/List.hs
+++ b/src/Hint/List.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE ViewPatterns, PatternGuards, FlexibleContexts #-}
 {-
     Find and match:
@@ -34,6 +35,8 @@
 foo = [x | False, x <- [1 .. 10]] -- []
 foo = [_ | x <- _, let _ = A{x}]
 issue1039 = foo (map f [1 | _ <- []]) -- [f 1 | _ <- []]
+{-# LANGUAGE OverloadedLists #-} \
+issue114 = True:[]
 </TEST>
 -}
 
@@ -41,37 +44,45 @@
 
 import Control.Applicative
 import Data.Generics.Uniplate.DataOnly
+import Data.List.NonEmpty qualified as NE
 import Data.List.Extra
 import Data.Maybe
 import Prelude
 
-import Hint.Type(DeclHint,Idea,suggest,ignore,substVars,toRefactSrcSpan,toSS)
+import Hint.Type(DeclHint,Idea,suggest,ignore,substVars,toRefactSrcSpan,toSSA,modComments,firstDeclComments)
 
 import Refact.Types hiding (SrcSpan)
-import qualified Refact.Types as R
+import Refact.Types qualified as R
 
 import GHC.Hs
-import SrcLoc
-import BasicTypes
-import RdrName
-import Name
-import FastString
-import TysWiredIn
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
+import GHC.Types.Name.Reader
+import GHC.Data.FastString
+import GHC.Builtin.Types
 
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
-import Language.Haskell.GhclibParserEx.GHC.Hs.Types
+import Language.Haskell.GhclibParserEx.GHC.Hs.Type
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
+
 listHint :: DeclHint
-listHint _ _ = listDecl
+listHint _ modu = listDecl overloadedListsOn
+  where
+    -- Comments appearing without a line-break before the first
+    -- declaration in a module are now associated with the declaration
+    -- not the module so to be safe, look also at `firstDeclComments
+    -- modu` (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
+    exts = concatMap snd (languagePragmas (pragmas (modComments modu) ++ pragmas (firstDeclComments modu)))
+    overloadedListsOn = "OverloadedLists" `elem` exts
 
-listDecl :: LHsDecl GhcPs -> [Idea]
-listDecl x =
-  concatMap (listExp False) (childrenBi x) ++
+listDecl :: Bool -> LHsDecl GhcPs -> [Idea]
+listDecl overloadedListsOn x =
+  concatMap (listExp overloadedListsOn False) (childrenBi x) ++
   stringType x ++
   concatMap listPat (childrenBi x) ++
   concatMap listComp (universeBi x)
@@ -84,7 +95,6 @@
   listCompCheckGuards o ListComp stmts
 listComp o@(L _ (HsDo _ MonadComp (L _ stmts))) =
   listCompCheckGuards o MonadComp stmts
-
 listComp (L _ HsPar{}) = [] -- App2 "sees through" paren, which causes duplicate hints with universeBi
 listComp o@(view -> App2 mp f (L _ (HsDo _ ListComp (L _ stmts)))) =
   listCompCheckMap o mp f ListComp stmts
@@ -92,46 +102,47 @@
   listCompCheckMap o mp f MonadComp stmts
 listComp _ = []
 
-listCompCheckGuards :: LHsExpr GhcPs -> HsStmtContext Name -> [ExprLStmt GhcPs] -> [Idea]
+listCompCheckGuards :: LHsExpr GhcPs -> HsDoFlavour -> [ExprLStmt GhcPs] -> [Idea]
 listCompCheckGuards o ctx stmts =
-  let revs = reverse stmts
-      e@(L _ LastStmt{}) = head revs -- In a ListComp, this is always last.
-      xs = reverse (tail revs) in
+  let revs = NE.reverse $ NE.fromList stmts
+      e@(L _ LastStmt{}) = NE.head revs -- In a ListComp, this is always last.
+      xs = reverse (NE.tail revs) in
   list_comp_aux e xs
   where
     list_comp_aux e xs
-      | "False" `elem` cons =  [suggest "Short-circuited list comprehension" o o' (suggestExpr o o')]
-      | "True" `elem` cons = [suggest "Redundant True guards" o o2 (suggestExpr o o2)]
-      | not (astListEq xs ys) = [suggest "Move guards forward" o o3 (suggestExpr o o3)]
+      | "False" `elem` cons =  [suggest "Short-circuited list comprehension" (reLoc o) (reLoc o') (suggestExpr o o')]
+      | "True" `elem` cons = [suggest "Redundant True guards" (reLoc o) (reLoc o2) (suggestExpr o o2)]
+      | not (astListEq xs ys) = [suggest "Move guards forward" (reLoc o) (reLoc o3) (suggestExpr o o3)]
       | otherwise = []
       where
         ys = moveGuardsForward xs
-        o' = noLoc $ ExplicitList noExtField Nothing []
-        o2 = noLoc $ HsDo noExtField ctx (noLoc (filter ((/= Just "True") . qualCon) xs ++ [e]))
-        o3 = noLoc $ HsDo noExtField ctx (noLoc $ ys ++ [e])
+        o' = noLocA $ ExplicitList noAnn []
+        o2 = noLocA $ HsDo noAnn ctx (noLocA (filter ((/= Just "True") . qualCon) xs ++ [e]))
+        o3 = noLocA $ HsDo noAnn ctx (noLocA $ ys ++ [e])
         cons = mapMaybe qualCon xs
         qualCon :: ExprLStmt GhcPs -> Maybe String
         qualCon (L _ (BodyStmt _ (L _ (HsVar _ (L _ x))) _ _)) = Just (occNameStr x)
         qualCon _ = Nothing
 
 listCompCheckMap ::
-  LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -> HsStmtContext Name -> [ExprLStmt GhcPs] -> [Idea]
+  LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -> HsDoFlavour -> [ExprLStmt GhcPs] -> [Idea]
 listCompCheckMap o mp f ctx stmts  | varToStr mp == "map" =
-    [suggest "Move map inside list comprehension" o o2 (suggestExpr o o2)]
+    [suggest "Move map inside list comprehension" (reLoc o) (reLoc o2) (suggestExpr o o2)]
     where
-      revs = reverse stmts
-      L _ (LastStmt _ body b s) = head revs -- In a ListComp, this is always last.
-      last = noLoc $ LastStmt noExtField (noLoc $ HsApp noExtField (paren f) (paren body)) b s
-      o2 =noLoc $ HsDo noExtField ctx (noLoc $ reverse (tail revs) ++ [last])
+      revs = NE.reverse $ NE.fromList stmts
+      L _ (LastStmt _ body b s) = NE.head revs -- In a ListComp, this is always last.
+      last = noLocA $ LastStmt noExtField (noLocA $ HsApp noExtField (paren f) (paren body)) b s
+      o2 =noLocA $ HsDo noAnn ctx (noLocA $ reverse (NE.tail revs) ++ [last])
+
 listCompCheckMap _ _ _ _ _ = []
 
 suggestExpr :: LHsExpr GhcPs -> LHsExpr GhcPs -> [Refactoring R.SrcSpan]
-suggestExpr o o2 = [Replace Expr (toSS o) [] (unsafePrettyPrint o2)]
+suggestExpr o o2 = [Replace Expr (toSSA o) [] (unsafePrettyPrint o2)]
 
 moveGuardsForward :: [ExprLStmt GhcPs] -> [ExprLStmt GhcPs]
 moveGuardsForward = reverse . f [] . reverse
   where
-    f guards (x@(L _ (BindStmt _ p _ _ _)) : xs) = reverse stop ++ x : f move xs
+    f guards (x@(L _ (BindStmt _ p _)) : xs) = reverse stop ++ x : f move xs
       where (move, stop) =
               span (if any hasPFieldsDotDot (universeBi x)
                        || any isPFieldWildcard (universeBi x)
@@ -149,31 +160,34 @@
     f guards (x@(L _ LetStmt{}):xs) = f (x:guards) xs
     f guards xs = reverse guards ++ xs
 
-listExp :: Bool -> LHsExpr GhcPs -> [Idea]
-listExp b (fromParen -> x) =
-  if null res then concatMap (listExp $ isAppend x) $ children x else [head res]
+listExp :: Bool -> Bool -> LHsExpr GhcPs -> [Idea]
+listExp overloadedListsOn b (fromParen -> x) =
+  if null res
+    then concatMap (listExp overloadedListsOn $ isAppend x) $ children x
+    else [NE.head $ NE.fromList res]
   where
-    res = [suggest name x x2 [r]
-          | (name, f) <- checks
+    res = [suggest name (reLoc x) (reLoc x2) [r]
+          | (name, f) <- checks overloadedListsOn
           , Just (x2, subts, temp) <- [f b x]
-          , let r = Replace Expr (toSS x) subts temp ]
+          , let r = Replace Expr (toSSA x) subts temp ]
 
 listPat :: LPat GhcPs -> [Idea]
-listPat x = if null res then concatMap listPat $ children x else [head res]
-    where res = [suggest name x x2 [r]
+listPat x = if null res then concatMap listPat $ children x else [NE.head $ NE.fromList res]
+    where res = [suggest name (reLoc x) (reLoc x2) [r]
                   | (name, f) <- pchecks
                   , Just (x2, subts, temp) <- [f x]
-                  , let r = Replace Pattern (toSS x) subts temp ]
+                  , let r = Replace Pattern (toSSA x) subts temp ]
+
 isAppend :: View a App2 => a -> Bool
 isAppend (view -> App2 op _ _) = varToStr op == "++"
 isAppend _ = False
 
-checks ::[(String, Bool -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String))]
-checks = let (*) = (,) in drop1 -- see #174
+checks :: Bool -> [(String, Bool -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String))]
+checks overloadedListsOn = let (*) = (,) in drop1 -- see #174
   [ "Use string literal" * useString
-  , "Use list literal" * useList
   , "Use :" * useCons
   ]
+  <> ["Use list literal" * useList | not overloadedListsOn ] -- see #114
 
 pchecks :: [(String, LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String))]
 pchecks = let (*) = (,) in drop1 -- see #174
@@ -183,16 +197,16 @@
 
 usePString :: LPat GhcPs -> Maybe (LPat GhcPs, [a], String)
 usePString (L _ (ListPat _ xs)) | not $ null xs, Just s <- mapM fromPChar xs =
-  let literal = noLoc $ LitPat noExtField (HsString NoSourceText (fsLit (show s)))
+  let literal = noLocA $ LitPat noExtField (HsString NoSourceText (fsLit (show s))) :: LPat GhcPs
   in Just (literal, [], unsafePrettyPrint literal)
 usePString _ = Nothing
 
 usePList :: LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String)
 usePList =
   fmap  ( (\(e, s) ->
-             (noLoc (ListPat noExtField e)
+             (noLocA (ListPat noAnn e)
              , map (fmap toRefactSrcSpan . fst) s
-             , unsafePrettyPrint (noLoc $ ListPat noExtField (map snd s) :: LPat GhcPs))
+             , unsafePrettyPrint (noLocA $ ListPat noAnn (map snd s) :: LPat GhcPs))
           )
           . unzip
         )
@@ -203,20 +217,20 @@
     f first _ _ = Nothing
 
     g :: String -> LPat GhcPs -> ((String, SrcSpan), LPat GhcPs)
-    g s (getLoc -> loc) = ((s, loc), noLoc $ VarPat noExtField (noLoc $ mkVarUnqual (fsLit s)))
+    g s (locA . getLoc -> loc) = ((s, loc), noLocA $ VarPat noExtField (noLocA $ mkVarUnqual (fsLit s)))
 
 useString :: p -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [a], String)
-useString b (L _ (ExplicitList _ _ xs)) | not $ null xs, Just s <- mapM fromChar xs =
-  let literal = noLoc (HsLit noExtField (HsString NoSourceText (fsLit (show s))))
+useString b (L _ (ExplicitList _ xs)) | not $ null xs, Just s <- mapM fromChar xs =
+  let literal = noLocA (HsLit noExtField (HsString NoSourceText (fsLit (show s)))) :: LHsExpr GhcPs
   in Just (literal, [], unsafePrettyPrint literal)
 useString _ _ = Nothing
 
 useList :: p -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String)
 useList b =
   fmap  ( (\(e, s) ->
-             (noLoc (ExplicitList noExtField Nothing e)
-             , map (fmap toSS) s
-             , unsafePrettyPrint (noLoc $ ExplicitList noExtField Nothing (map snd s) :: LHsExpr GhcPs))
+             (noLocA (ExplicitList noAnn e)
+             , map (fmap toSSA) s
+             , unsafePrettyPrint (noLocA $ ExplicitList noAnn (map snd s) :: LHsExpr GhcPs))
           )
           . unzip
         )
@@ -235,41 +249,41 @@
                                     , Just (newX, tplX, spanX) <- f x
                                     , not $ isAppend y =
     Just (gen newX y
-         , [("x", spanX), ("xs", toSS y)]
+         , [("x", spanX), ("xs", toSSA y)]
          , unsafePrettyPrint $ gen tplX (strToVar "xs")
          )
   where
     f :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, R.SrcSpan)
-    f (L _ (ExplicitList _ _ [x]))
-      | isAtom x || isApp x = Just (x, strToVar "x", toSS x)
-      | otherwise = Just (addParen x, addParen (strToVar "x"), toSS x)
+    f (L _ (ExplicitList _ [x]))
+      | isAtom x || isApp x = Just (x, strToVar "x", toSSA x)
+      | otherwise = Just (addParen x, addParen (strToVar "x"), toSSA x)
     f _ = Nothing
 
     gen :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
-    gen x = noLoc . OpApp noExtField x (noLoc (HsVar noExtField  (noLoc consDataCon_RDR)))
+    gen x = noLocA . OpApp noExtField x (noLocA (HsVar noExtField  (noLocA consDataCon_RDR)))
 useCons _ _ = Nothing
 
 typeListChar :: LHsType GhcPs
 typeListChar =
-  noLoc $ HsListTy noExtField
-    (noLoc (HsTyVar noExtField NotPromoted (noLoc (mkVarUnqual (fsLit "Char")))))
+  noLocA $ HsListTy noAnn
+    (noLocA (HsTyVar noAnn NotPromoted (noLocA (mkVarUnqual (fsLit "Char")))))
 
 typeString :: LHsType GhcPs
 typeString =
-  noLoc $ HsTyVar noExtField NotPromoted (noLoc (mkVarUnqual (fsLit "String")))
+  noLocA $ HsTyVar noAnn NotPromoted (noLocA (mkVarUnqual (fsLit "String")))
 
 stringType :: LHsDecl GhcPs  -> [Idea]
 stringType (L _ x) = case x of
   InstD _ ClsInstD{
     cid_inst=
         ClsInstDecl{cid_binds=x, cid_tyfam_insts=y, cid_datafam_insts=z}} ->
-    f x ++ f y ++ f z -- Pretty much everthing but the instance type.
+    f x ++ f y ++ f z -- Pretty much everything but the instance type.
   _ -> f x
   where
     f x = concatMap g $ childrenBi x
 
     g :: LHsType GhcPs -> [Idea]
-    g e@(fromTyParen -> x) = [ignore "Use String" x (transform f x)
+    g e@(fromTyParen -> x) = [ignore "Use String" (reLoc x) (reLoc (transform f x))
                               rs | not . null $ rs]
       where f x = if astEq x typeListChar then typeString else x
-            rs = [Replace Type (toSS t) [] (unsafePrettyPrint typeString) | t <- universe x, astEq t typeListChar]
+            rs = [Replace Type (toSSA t) [] (unsafePrettyPrint typeString) | t <- universe x, astEq t typeListChar]
diff --git a/src/Hint/ListRec.hs b/src/Hint/ListRec.hs
--- a/src/Hint/ListRec.hs
+++ b/src/Hint/ListRec.hs
@@ -31,7 +31,7 @@
 
 module Hint.ListRec(listRecHint) where
 
-import Hint.Type (DeclHint, Severity(Suggestion, Warning), idea, toSS)
+import Hint.Type (DeclHint, Severity(Suggestion, Warning), idea, toSSA)
 
 import Data.Generics.Uniplate.DataOnly
 import Data.List.Extra
@@ -40,17 +40,20 @@
 import Control.Monad
 import Refact.Types hiding (RType(Match))
 
-import SrcLoc
+import GHC.Types.SrcLoc
 import GHC.Hs.Extension
 import GHC.Hs.Pat
-import GHC.Hs.Types
-import TysWiredIn
-import RdrName
+import GHC.Builtin.Types
+import GHC.Hs.Type
+import GHC.Types.Name.Reader
 import GHC.Hs.Binds
 import GHC.Hs.Expr
 import GHC.Hs.Decls
-import BasicTypes
+import GHC.Types.Basic
 
+import GHC.Parser.Annotation
+import Language.Haskell.Syntax.Extension
+
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
@@ -69,7 +72,7 @@
             guard $ recursiveStr `notElem` varss y
             -- Maybe we can do better here maintaining source
             -- formatting?
-            pure $ idea severity ("Use " ++ use) o y [Replace Decl (toSS o) [] (unsafePrettyPrint y)]
+            pure $ idea severity ("Use " ++ use) (reLoc o) (reLoc y) [Replace Decl (toSSA o) [] (unsafePrettyPrint y)]
 
 recursiveStr :: String
 recursiveStr = "_recursive_"
@@ -93,7 +96,6 @@
     Int -- list position
     BList (LHsExpr GhcPs) -- list type/body
 
-
 ---------------------------------------------------------------------
 -- MATCH THE RECURSION
 
@@ -119,7 +121,7 @@
       appsBracket [ strToVar "foldl", niceLambda [v,x] lhs, strToVar v, strToVar xs]
     -- Suggest 'foldM'?
     | [v] <- vs, (L _ (HsApp _ ret res)) <- nil, isReturn ret, varToStr res == "()" || view res == Var_ v
-    , [L _ (BindStmt _ (view -> PVar_ b1) e _ _), L _ (BodyStmt _ (fromParen -> (L _ (HsApp _ r (view -> Var_ b2)))) _ _)] <- asDo cons
+    , [L _ (BindStmt _ (view -> PVar_ b1) e), L _ (BodyStmt _ (fromParen -> (L _ (HsApp _ r (view -> Var_ b2)))) _ _)] <- asDo cons
     , b1 == b2, astEq r recursive, xs `notElem` vars e
     , name <- "foldM" ++ ['_' | varToStr res == "()"]
     = Just $ (,,) name Suggestion $
@@ -132,19 +134,19 @@
 asDo :: LHsExpr GhcPs -> [LStmt GhcPs (LHsExpr GhcPs)]
 asDo (view ->
        App2 bind lhs
-         (L _ (HsLam _ MG {
-              mg_origin=FromSource
+         (L _ (HsLam _ LamSingle MG {
+              mg_ext=FromSource
             , mg_alts=L _ [
-                 L _ Match {  m_ctxt=LambdaExpr
-                            , m_pats=[v@(L _ VarPat{})]
+                 L _ Match {  m_ctxt=(LamAlt LamSingle)
+                            , m_pats=L _ [v@(L _ VarPat{})]
                             , m_grhss=GRHSs _
                                         [L _ (GRHS _ [] rhs)]
-                                        (L _ (EmptyLocalBinds _))}]}))
+                                        (EmptyLocalBinds _)}]}))
       ) =
-  [ noLoc $ BindStmt noExtField v lhs noSyntaxExpr noSyntaxExpr
-  , noLoc $ BodyStmt noExtField rhs noSyntaxExpr noSyntaxExpr ]
-asDo (L _ (HsDo _ DoExpr (L _ stmts))) = stmts
-asDo x = [noLoc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr]
+  [ noLocA $ BindStmt noAnn v lhs
+  , noLocA $ BodyStmt noExtField rhs noSyntaxExpr noSyntaxExpr ]
+asDo (L _ (HsDo _ (DoExpr _) (L _ stmts))) = stmts
+asDo x = [noLocA $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr]
 
 
 ---------------------------------------------------------------------
@@ -155,7 +157,7 @@
 findCase x = do
   -- Match a function binding with two alternatives.
   (L _ (ValD _ FunBind {fun_matches=
-              MG{mg_origin=FromSource, mg_alts=
+              MG{mg_ext=FromSource, mg_alts=
                      (L _
                             [ x1@(L _ Match{..}) -- Match fields.
                             , x2]), ..} -- Match group fields.
@@ -170,14 +172,14 @@
   (ps, b2) <- pure $ eliminateArgs ps1 b2
 
   let ps12 = let (a, b) = splitAt p1 ps1 in map strToPat (a ++ xs : b) -- Function arguments.
-      emptyLocalBinds = noLoc $ EmptyLocalBinds noExtField -- Empty where clause.
-      gRHS e = noLoc $ GRHS noExtField [] e :: LGRHS GhcPs (LHsExpr GhcPs) -- Guarded rhs.
-      gRHSSs e = GRHSs noExtField [gRHS e] emptyLocalBinds -- Guarded rhs set.
-      match e = Match{m_ext=noExtField,m_pats=ps12, m_grhss=gRHSSs e, ..} -- Match.
-      matchGroup e = MG{mg_alts=noLoc [noLoc $ match e], mg_origin=Generated, ..} -- Match group.
+      emptyLocalBinds = EmptyLocalBinds noExtField :: HsLocalBindsLR GhcPs GhcPs -- Empty where clause.
+      gRHS e = noLocA $ GRHS noAnn [] e :: LGRHS GhcPs (LHsExpr GhcPs) -- Guarded rhs.
+      gRHSSs e = GRHSs emptyComments [gRHS e] emptyLocalBinds -- Guarded rhs set.
+      match e = Match{m_ext=noExtField,m_pats=noLocA ps12, m_grhss=gRHSSs e, ..} -- Match.
+      matchGroup e = MG{mg_alts=noLocA [noLocA $ match e], mg_ext=Generated OtherExpansion SkipPmc, ..} -- Match group.
       funBind e = FunBind {fun_matches=matchGroup e, ..} :: HsBindLR GhcPs GhcPs -- Fun bind.
 
-  pure (ListCase ps b1 (x, xs, b2), noLoc . ValD noExtField . funBind)
+  pure (ListCase ps b1 (x, xs, b2), noLocA . ValD noExtField . funBind)
 
 delCons :: String -> Int -> String -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)
 delCons func pos var (fromApps -> (view -> Var_ x) : xs) | func == x = do
@@ -207,10 +209,10 @@
             , m_pats = ps
             , m_grhss =
               GRHSs {grhssGRHSs=[L l (GRHS _ [] body)]
-                        , grhssLocalBinds=L _ (EmptyLocalBinds _)
+                        , grhssLocalBinds=EmptyLocalBinds _
                         }
             } <- pure x
-  (a, b, c) <- findPat ps
+  (a, b, c) <- findPat (unLoc ps)
   pure $ Branch (occNameStr name) a b c $ simplifyExp body
 
 findPat :: [LPat GhcPs] -> Maybe ([String], Int, BList)
@@ -223,8 +225,8 @@
 
 readPat :: LPat GhcPs -> Maybe (Either String BList)
 readPat (view -> PVar_ x) = Just $ Left x
-readPat (L _ (ParPat _ (L _ (ConPatIn (L _ n) (InfixCon (view -> PVar_ x) (view -> PVar_ xs))))))
+readPat (L _ (ParPat _ (L _ (ConPat _ (L _ n) (InfixCon (view -> PVar_ x) (view -> PVar_ xs))))))
  | n == consDataCon_RDR = Just $ Right $ BCons x xs
-readPat (L _ (ConPatIn (L _ n) (PrefixCon [])))
+readPat (L _ (ConPat _ (L _ n) (PrefixCon [] [])))
   | n == nameRdrName nilDataConName = Just $ Right BNil
 readPat _ = Nothing
diff --git a/src/Hint/Match.hs b/src/Hint/Match.hs
--- a/src/Hint/Match.hs
+++ b/src/Hint/Match.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE RecordWildCards, NamedFieldPuns, TupleSections #-}
 {-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts #-}
 
@@ -39,11 +40,12 @@
 
 module Hint.Match(readMatch) where
 
-import Hint.Type (ModuleEx,Idea,idea,ideaNote,toSS)
+import Hint.Type (ModuleEx,Idea,idea,ideaNote,toSSA)
+
 import Util
 import Timing
-import qualified Data.Set as Set
-import qualified Refact.Types as R
+import Data.Set qualified as Set
+import Refact.Types qualified as R
 
 import Control.Monad
 import Data.Tuple.Extra
@@ -51,12 +53,11 @@
 import Config.Type
 import Data.Generics.Uniplate.DataOnly
 
-import Bag
 import GHC.Hs
-import SrcLoc
-import BasicTypes
-import RdrName
-import OccName
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
 import Data.Data
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
@@ -99,8 +100,8 @@
 
   --   If a == b then
   --   x is 'a', op is '==' and y is 'b' and,
-  let lSec = addParen (cL l (SectionL noExtField x op)) -- (a == )
-      rSec = addParen (cL l (SectionR noExtField op y)) -- ( == b)
+  let lSec = addParen (L l (SectionL noExtField x op)) -- (a == )
+      rSec = addParen (L l (SectionR noExtField op y)) -- ( == b)
   in (first (lSec :) <$> dotVersion y) ++ (first (rSec :) <$> dotVersion x) -- [([(a ==)], b), ([(b == )], a])].
 dotVersion _ = []
 
@@ -109,17 +110,17 @@
 
 findIdeas :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea]
 findIdeas matches s _ decl = timed "Hint" "Match apply" $ forceList
-    [ (idea (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes}
+    [ (idea (hintRuleSeverity m) (hintRuleName m) (reLoc x) (reLoc y) [r]){ideaNote=notes}
     | (name, expr) <- findDecls decl
     , (parent,x) <- universeParentExp expr
     , m <- matches, Just (y, tpl, notes, subst) <- [matchIdea s name m parent x]
-    , let r = R.Replace R.Expr (toSS x) subst (unsafePrettyPrint tpl)
+    , let r = R.Replace R.Expr (toSSA x) subst (unsafePrettyPrint tpl)
     ]
 
 -- | A list of root expressions, with their associated names
 findDecls :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)]
 findDecls x@(L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) =
-    [(fromMaybe "" $ bindName xs, x) | xs <- bagToList cid_binds, x <- childrenBi xs]
+    [(fromMaybe "" $ bindName xs, x) | xs <- cid_binds, x <- childrenBi xs]
 findDecls (L _ RuleD{}) = [] -- Often rules contain things that HLint would rewrite.
 findDecls x = map (fromMaybe "" $ declName x,) $ childrenBi x
 
@@ -134,14 +135,15 @@
       rhs = unextendInstances hintRuleRHS
       sa  = hintRuleScope
       nm a b = scopeMatch (sa, a) (sb, b)
+
   (u, extra) <- unifyExp nm True lhs x
   u <- validSubst astEq u
 
   -- Need to check free vars before unqualification, but after subst
   -- (with 'e') need to unqualify before substitution (with 'res').
-  let rhs' | Just fun <- extra = rebracket1 $ noLoc (HsApp noExtField fun rhs)
+  let rhs' | Just fun <- extra = rebracket1 $ noLocA (HsApp noExtField fun rhs)
            | otherwise = rhs
-      (e, tpl) = substitute u rhs'
+      (e, (tpl, substNoParens)) = substitute u rhs'
       noParens = [varToStr $ fromParen x | L _ (HsApp _ (varToStr -> "_noParen_") x) <- universe tpl]
 
   u <- pure (removeParens noParens u)
@@ -154,15 +156,19 @@
   -- we have lambdas we might be moving, and QuasiQuotes, we might
   -- inadvertantly break free vars because quasi quotes don't show
   -- what free vars they make use of.
-  guard $ not (any isLambda $ universe lhs) || not (any isQuasiQuote $ universe x)
+  guard $ not (any isLambda $ universe lhs) || not (any isQuasiQuoteExpr $ universe x)
 
   guard $ checkSide (unextendInstances <$> hintRuleSide) $ ("original", x) : ("result", res) : fromSubst u
   guard $ checkDefine declName parent rhs
 
-  (u, tpl) <- pure $ if any ((== noSrcSpan) . getLoc . snd) (fromSubst u) then (mempty, res) else (u, tpl)
-  tpl <- pure $ unqualify sa sb (performSpecial tpl)
+  (u, tpl) <- pure $ if any ((== noSrcSpan) . locA . getLoc . snd) (fromSubst u) then (mempty, res) else (u, tpl)
+  tpl <- pure $ unqualify sa sb (addBracket parent $ performSpecial tpl)
 
-  pure (res, tpl, hintRuleNotes, [(s, toSS pos) | (s, pos) <- fromSubst u, getLoc pos /= noSrcSpan])
+  pure ( res, tpl, hintRuleNotes,
+         [ (s, toSSA pos') | (s, pos) <- fromSubst u, locA (getLoc pos) /= noSrcSpan
+                          , let pos' = if s `elem` substNoParens then fromParen pos else pos
+         ]
+       )
 
 ---------------------------------------------------------------------
 -- SIDE CONDITIONS
@@ -194,13 +200,13 @@
       isType "Compare" x = True -- Just a hint for proof stuff
       isType "Atom" x = isAtom x
       isType "WHNF" x = isWHNF x
-      isType "Wildcard" x = any isFieldPun (universeBi x) || any hasFieldsDotDot (universeBi x)
+      isType "Wildcard" x = any hasFieldsDotDot (universeBi x)
       isType "Nat" (asInt -> Just x) | x >= 0 = True
       isType "Pos" (asInt -> Just x) | x >  0 = True
       isType "Neg" (asInt -> Just x) | x <  0 = True
       isType "NegZero" (asInt -> Just x) | x <= 0 = True
       isType "LitInt" (L _ (HsLit _ HsInt{})) = True
-      isType "LitInt" (L _ (HsOverLit _ (OverLit _ HsIntegral{} _))) = True
+      isType "LitInt" (L _ (HsOverLit _ (OverLit _ HsIntegral{}))) = True
       isType "LitString" (L _ (HsLit _ HsString{})) = True
       isType "Var" (L _ HsVar{}) = True
       isType "App" (L _ HsApp{}) = True
@@ -215,12 +221,12 @@
       asInt :: LHsExpr GhcPs -> Maybe Integer
       asInt (L _ (HsPar _ x)) = asInt x
       asInt (L _ (NegApp _ x _)) = negate <$> asInt x
-      asInt (L _ (HsLit _ (HsInt _ (IL _ neg x)) )) = Just $ if neg then -x else x
-      asInt (L _ (HsOverLit _ (OverLit _ (HsIntegral (IL _ neg x)) _))) = Just $ if neg then -x else x
+      asInt (L _ (HsLit _ (HsInt _ (IL _ _ x)) )) = Just x
+      asInt (L _ (HsOverLit _ (OverLit _ (HsIntegral (IL _ _ x))))) = Just x
       asInt _ = Nothing
 
       list :: LHsExpr GhcPs -> [LHsExpr GhcPs]
-      list (L _ (ExplicitList _ _ xs)) = xs
+      list (L _ (ExplicitList _ xs)) = xs
       list x = [x]
 
       sub :: LHsExpr GhcPs -> LHsExpr GhcPs
@@ -231,10 +237,10 @@
 -- Does the result look very much like the declaration?
 checkDefine :: String -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
 checkDefine declName Nothing y =
-  let funOrOp expr = case expr of
+  let funOrOp expr = (case expr of
         L _ (HsApp _ fun _) -> funOrOp fun
         L _ (OpApp _ _ op _) -> funOrOp op
-        other -> other
+        other -> other) :: LHsExpr GhcPs
    in declName /= varToStr (transformBi unqual $ funOrOp y)
 checkDefine _ _ _ = True
 
@@ -253,12 +259,12 @@
 unqualify :: Scope -> Scope -> LHsExpr GhcPs -> LHsExpr GhcPs
 unqualify from to = transformBi f
   where
-    f :: Located RdrName -> Located RdrName
+    f :: LocatedN RdrName -> LocatedN RdrName
     f x@(L _ (Unqual s)) | isUnifyVar (occNameString s) = x
     f x = scopeMove (from, x) to
 
 addBracket :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs
-addBracket (Just (i, p)) c | needBracketOld i p c = noLoc $ HsPar noExtField c
+addBracket (Just (i, p)) c | needBracketOld i p c = nlHsPar c
 addBracket _ x = x
 
 -- Type substitution e.g. 'Foo Int' for 'a' in 'Proxy a' can lead to a
@@ -269,5 +275,5 @@
   where
     f :: LHsType GhcPs -> LHsType GhcPs
     f (L _ (HsAppTy _ t x@(L _ HsAppTy{}))) =
-      noLoc (HsAppTy noExtField t (noLoc (HsParTy noExtField x)))
+      noLocA (HsAppTy noExtField t (noLocA (HsParTy noAnn x)))
     f x = x
diff --git a/src/Hint/Monad.hs b/src/Hint/Monad.hs
--- a/src/Hint/Monad.hs
+++ b/src/Hint/Monad.hs
@@ -1,4 +1,11 @@
-{-# LANGUAGE LambdaCase, ViewPatterns, PatternGuards, FlexibleContexts #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns #-}
+
 {-
     Find and match:
 
@@ -45,7 +52,9 @@
 main = do f a $ sleep 10 -- @Ignore
 main = do foo x; return 3; bar z --
 main = void $ forM_ f xs -- forM_ f xs
-main = void $ forM f xs -- void $ forM_ f xs
+main = void (forM_ f xs) -- forM_ f xs
+main = void $ forM f xs -- forM_ f xs
+main = void (forM f xs) -- forM_ f xs
 main = do _ <- forM_ f xs; bar -- forM_ f xs
 main = do bar; forM_ f xs; return () -- do bar; forM_ f xs
 main = do a; when b c; return () -- do a; when b c
@@ -63,12 +72,13 @@
 import Hint.Type
 
 import GHC.Hs hiding (Warning)
-import SrcLoc
-import BasicTypes
-import TcEvidence
-import RdrName
-import OccName
-import Bag
+import GHC.Types.Fixity
+import GHC.Types.SrcLoc
+import GHC.Types.Basic
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
+import GHC.Data.Strict qualified
+
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
@@ -80,7 +90,7 @@
 import Data.Maybe
 import Data.List.Extra
 import Refact.Types hiding (Match)
-import qualified Refact.Types as R
+import Refact.Types qualified as R
 
 
 badFuncs :: [String]
@@ -109,30 +119,49 @@
   case x of
     (view -> App2 op x1 x2) | isTag ">>" op -> f x1
     (view -> App2 op x1 (view -> LamConst1 _)) | isTag ">>=" op -> f x1
-    (L l (HsApp _ op x)) | isTag "void" op -> seenVoid (cL l . HsApp noExtField op) x
-    (L l (OpApp _ op dol x)) | isTag "void" op, isDol dol -> seenVoid (cL l . OpApp noExtField op dol) x
+    (L l (HsApp _ op x)) | isTag "void" op -> seenVoid (L l . HsApp noExtField op) x
+    (L l (OpApp _ op dol x)) | isTag "void" op, isDol dol -> seenVoid (L l . OpApp noExtField op dol) x
     (L loc (HsDo _ ctx (L loc2 [L loc3 (BodyStmt _ y _ _ )]))) ->
-      let doOrMDo = case ctx of MDoExpr -> "mdo"; _ -> "do"
-       in [ ideaRemove Ignore ("Redundant " ++ doOrMDo) (doSpan doOrMDo loc) doOrMDo [Replace Expr (toSS x) [("y", toSS y)] "y"]
+      let doOrMDo = case ctx of MDoExpr _ -> "mdo"; _ -> "do"
+       in [ ideaRemove Ignore ("Redundant " ++ doOrMDo) (doSpan doOrMDo (locA loc)) doOrMDo [Replace Expr (toSSA x) [("y", toSSA y)] "y"]
           | not $ doAsBrackets parentExpr y
           , not $ doAsAvoidingIndentation parentDo x
           ]
-    (L loc (HsDo _ DoExpr (L _ xs))) ->
-      monadSteps (cL loc . HsDo noExtField DoExpr . noLoc) xs ++
-      [suggest "Use let" from to [r] | (from, to, r) <- monadLet xs] ++
+    (L loc (HsDo _ (DoExpr mm) (L _ xs))) ->
+      monadSteps (L loc . HsDo noAnn (DoExpr mm) . noLocA) xs ++
+      [suggest "Use let" (reLoc from) (reLoc to) [r] | (from, to, r) <- monadLet xs] ++
       concat [f x | (L _ (BodyStmt _ x _ _)) <- dropEnd1 xs] ++
-      concat [f x | (L _ (BindStmt _ (LL _ WildPat{}) x _ _)) <- dropEnd1 xs]
+      concat [f x | (L _ (BindStmt _ (L _ WildPat{}) x)) <- dropEnd1 xs]
     _ -> []
   where
     f = monadNoResult (fromMaybe "" decl) id
-    seenVoid wrap x = monadNoResult (fromMaybe "" decl) wrap x
-      ++ [warn "Redundant void" (wrap x) x [Replace Expr (toSS (wrap x)) [("a", toSS x)] "a"] | returnsUnit x]
+    seenVoid wrap (L l (HsPar x y)) = seenVoid (wrap . L l . \y -> HsPar x y) y
+    seenVoid wrap x =
+      -- Suggest `traverse_ f x` given `void $ traverse_ f x`
+      [warn "Redundant void" (reLoc (wrap x)) (reLoc x) [Replace Expr (toSSA (wrap x)) [("a", toSSA x)] "a"] | returnsUnit x]
+        -- Suggest `traverse_ f x` given `void $ traverse f x`
+        ++ ( case modifyAppHead
+               ( \fun@(L l name) ->
+                   ( if occNameStr name `elem` badFuncs
+                       then L l (mkRdrUnqual (mkVarOcc (occNameStr name ++ "_")))
+                       else fun,
+                     fun
+                   )
+               )
+               x of
+               (x', Just fun@(L l name)) | occNameStr name `elem` badFuncs ->
+                  let fun_ = occNameStr name ++ "_"
+                   in [warn ("Use " ++ fun_) (reLoc (wrap x)) (reLoc x')
+                        [Replace Expr (toSSA (wrap x)) [("a", toSSA x)] "a",
+                         Replace Expr (toSSA fun) [] fun_]]
+               _ -> []
+           )
     doSpan doOrMDo = \case
       UnhelpfulSpan s -> UnhelpfulSpan s
-      RealSrcSpan s ->
+      RealSrcSpan s _ ->
         let start = realSrcSpanStart s
             end = mkRealSrcLoc (srcSpanFile s) (srcLocLine start) (srcLocCol start + length doOrMDo)
-         in RealSrcSpan (mkRealSrcSpan start end)
+         in RealSrcSpan (mkRealSrcSpan start end) GHC.Data.Strict.Nothing
 
 -- Sometimes people write 'a * do a + b', to avoid brackets,
 -- or using BlockArguments they can write 'a do a b',
@@ -144,35 +173,46 @@
 doAsBrackets Nothing x = False
 
 
--- Sometimes people write do, to avoid identation, see
+-- Sometimes people write do, to avoid indentation, see
 -- https://github.com/ndmitchell/hlint/issues/978
--- Return True if they are using do as avoiding identation
+-- Return True if they are using do as avoiding indentation
 doAsAvoidingIndentation :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
-doAsAvoidingIndentation (Just (L _ (HsDo _ _ (L (RealSrcSpan a) _)))) (L _ (HsDo _ _ (L (RealSrcSpan b) _)))
-    = srcSpanStartCol a == srcSpanStartCol b
+doAsAvoidingIndentation (Just (L _ (HsDo _ _ (L anna _)))) (L _ (HsDo _ _ (L annb _)))
+  | EpAnn (EpaSpan (RealSrcSpan a _)) _ _ <- anna
+  , EpAnn (EpaSpan (RealSrcSpan b _)) _ _ <- annb
+  = srcSpanStartCol a == srcSpanStartCol b
 doAsAvoidingIndentation parent self = False
 
+-- Apply a function to the application head, including `head arg` and `head $ arg`, which modifies
+-- the head and returns a value. Sees through parentheses.
+modifyAppHead :: forall a. (LIdP GhcPs -> (LIdP GhcPs, a)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, Maybe a)
+modifyAppHead f = go id
+  where
+    go :: (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> (LHsExpr GhcPs, Maybe a)
+    go wrap (L l (HsPar _ x)) = go (wrap . L l . \y -> HsPar noAnn y) x
+    go wrap (L l (HsApp _ x y)) = go (\x -> wrap $ L l (HsApp noExtField x y)) x
+    go wrap (L l (OpApp _ x op y)) | isDol op = go (\x -> wrap $ L l (OpApp noExtField x op y)) x
+    go wrap (L l (HsVar _ x)) = (wrap (L l (HsVar NoExtField x')), Just a)
+      where (x', a) = f x
+    go _ expr = (expr, Nothing)
 
 returnsUnit :: LHsExpr GhcPs -> Bool
-returnsUnit (L _ (HsPar _ x)) = returnsUnit x
-returnsUnit (L _ (HsApp _ x _)) = returnsUnit x
-returnsUnit (L _ (OpApp _ x op _)) | isDol op = returnsUnit x
-returnsUnit (L _ (HsVar _ (L _ x))) = occNameStr x `elem` map (++ "_") badFuncs ++ unitFuncs
-returnsUnit _ = False
+returnsUnit = fromMaybe False
+            . snd
+            . modifyAppHead (\x -> (x, occNameStr (unLoc x) `elem` map (++ "_") badFuncs ++ unitFuncs))
 
 -- See through HsPar, and down HsIf/HsCase, return the name to use in
 -- the hint, and the revised expression.
 monadNoResult :: String -> (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea]
-monadNoResult inside wrap (L l (HsPar _ x)) = monadNoResult inside (wrap . cL l . HsPar noExtField) x
-monadNoResult inside wrap (L l (HsApp _ x y)) = monadNoResult inside (\x -> wrap $ cL l (HsApp noExtField x y)) x
+monadNoResult inside wrap (L l (HsPar _ x)) = monadNoResult inside (wrap . nlHsPar) x
+monadNoResult inside wrap (L l (HsApp _ x y)) = monadNoResult inside (\x -> wrap $ L l (HsApp noExtField x y)) x
 monadNoResult inside wrap (L l (OpApp _ x tag@(L _ (HsVar _ (L _ op))) y))
-    | isDol tag = monadNoResult inside (\x -> wrap $ cL l (OpApp noExtField x tag y)) x
-    | occNameStr op == ">>=" = monadNoResult inside (wrap . cL l . OpApp noExtField x tag) y
+    | isDol tag = monadNoResult inside (\x -> wrap $ L l (OpApp noExtField x tag y)) x
+    | occNameStr op == ">>=" = monadNoResult inside (wrap . L l . OpApp noExtField x tag) y
 monadNoResult inside wrap x
     | x2 : _ <- filter (`isTag` x) badFuncs
     , let x3 = x2 ++ "_"
-
-    = [warn ("Use " ++ x3) (wrap x) (wrap $ strToVar x3) [Replace Expr (toSS x) [] x3] | inside /= x3]
+    = [warn ("Use " ++ x3) (reLoc (wrap x)) (reLoc (wrap $ strToVar x3)) [Replace Expr (toSSA x) [] x3] | inside /= x3]
 monadNoResult inside wrap (replaceBranches -> (bs, rewrap)) =
     map (\x -> x{ideaNote=nubOrd $ Note "May require adding void to other branches" : ideaNote x}) $ concat
         [monadNoResult inside id b | b <- bs]
@@ -182,46 +222,46 @@
 
 -- Rewrite 'do return x; $2' as 'do $2'.
 monadStep wrap (o@(L _ (BodyStmt _ (fromRet -> Just (ret, _)) _ _ )) : xs@(_:_))
-  = [ideaRemove Warning ("Redundant " ++ ret) (getLoc o) (unsafePrettyPrint o) [Delete Stmt (toSS o)]]
+  = [ideaRemove Warning ("Redundant " ++ ret) (locA (getLoc o)) (unsafePrettyPrint o) [Delete Stmt (toSSA o)]]
 
 -- Rewrite 'do a <- $1; return a' as 'do $1'.
-monadStep wrap o@[ g@(L _ (BindStmt _ (LL _ (VarPat _ (L _ p))) x _ _ ))
+monadStep wrap o@[ g@(L _ (BindStmt _ (L _ (VarPat _ (L _ p))) x))
                   , q@(L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ v)))) _ _))]
   | occNameStr p == occNameStr v
-  = [warn ("Redundant " ++ ret) (wrap o) (wrap [noLoc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr])
-      [Replace Stmt (toSS g) [("x", toSS x)] "x", Delete Stmt (toSS q)]]
+  = [warn ("Redundant " ++ ret) (reLoc (wrap o)) (reLoc (wrap [noLocA $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr]))
+      [Replace Stmt (toSSA g) [("x", toSSA x)] "x", Delete Stmt (toSSA q)]]
 
 -- Suggest to use join. Rewrite 'do x <- $1; x; $2' as 'do join $1; $2'.
-monadStep wrap o@(g@(L _ (BindStmt _ (view -> PVar_ p) x _ _)):q@(L _ (BodyStmt _ (view -> Var_ v) _ _)):xs)
+monadStep wrap o@(g@(L _ (BindStmt _ (view -> PVar_ p) x)):q@(L _ (BodyStmt _ (view -> Var_ v) _ _)):xs)
   | p == v && v `notElem` varss xs
-  = let app = noLoc $ HsApp noExtField (strToVar "join") x
-        body = noLoc $ BodyStmt noExtField (rebracket1 app) noSyntaxExpr noSyntaxExpr
+  = let app = noLocA $ HsApp noExtField (strToVar "join") x
+        body = noLocA $ BodyStmt noExtField (rebracket1 app) noSyntaxExpr noSyntaxExpr
         stmts = body : xs
-    in [warn "Use join" (wrap o) (wrap stmts) r]
-  where r = [Replace Stmt (toSS g) [("x", toSS x)] "join x", Delete Stmt (toSS q)]
+    in [warn "Use join" (reLoc (wrap o)) (reLoc (wrap stmts)) r]
+  where r = [Replace Stmt (toSSA g) [("x", toSSA x)] "join x", Delete Stmt (toSSA q)]
 
 -- Redundant variable capture. Rewrite 'do _ <- <return ()>; $1' as
 -- 'do <return ()>; $1'.
-monadStep wrap (o@(L loc (BindStmt _ p x _ _)) : rest)
+monadStep wrap (o@(L loc (BindStmt _ p x)) : rest)
     | isPWildcard p, returnsUnit x
-    = let body = cL loc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr :: ExprLStmt GhcPs
-      in [warn "Redundant variable capture" o body [Replace Stmt (toSS o) [("x", toSS x)] "x"]]
+    = let body = L loc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr :: ExprLStmt GhcPs
+      in [warn "Redundant variable capture" (reLoc o) (reLoc body) [Replace Stmt (toSSA o) [("x", toSSA x)] "x"]]
 
 -- Redundant unit return : 'do <return ()>; return ()'.
 monadStep
   wrap o@[ L _ (BodyStmt _ x _ _)
          , q@(L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ unit)))) _ _))]
      | returnsUnit x, occNameStr unit == "()"
-  = [warn ("Redundant " ++ ret) (wrap o) (wrap $ take 1 o) [Delete Stmt (toSS q)]]
+  = [warn ("Redundant " ++ ret) (reLoc (wrap o)) (reLoc (wrap $ take 1 o)) [Delete Stmt (toSSA q)]]
 
 -- Rewrite 'do x <- $1; return $ f $ g x' as 'f . g <$> x'
 monadStep wrap
-  o@[g@(L _ (BindStmt _ (view -> PVar_ u) x _ _))
+  o@[g@(L _ (BindStmt _ (view -> PVar_ u) x))
     , q@(L _ (BodyStmt _ (fromApplies -> (ret:f:fs, view -> Var_ v)) _ _))]
   | isReturn ret, notDol x, u == v, length fs < 3, all isSimple (f : fs), v `notElem` vars (f : fs)
   =
-      [warn "Use <$>" (wrap o) (wrap [noLoc $ BodyStmt noExtField (noLoc $ OpApp noExtField (foldl' (\acc e -> noLoc $ OpApp noExtField acc (strToVar ".") e) f fs) (strToVar "<$>") x) noSyntaxExpr noSyntaxExpr])
-      [Replace Stmt (toSS g) (("x", toSS x):zip vs (toSS <$> f:fs)) (intercalate " . " (take (length fs + 1) vs) ++ " <$> x"), Delete Stmt (toSS q)]]
+      [warn "Use <$>" (reLoc (wrap o)) (reLoc (wrap [noLocA $ BodyStmt noExtField (noLocA $ OpApp noExtField (foldl' (\acc e -> noLocA $ OpApp noExtField acc (strToVar ".") e) f fs) (strToVar "<$>") x) noSyntaxExpr noSyntaxExpr]))
+      [Replace Stmt (toSSA g) (("x", toSSA x):zip vs (toSSA <$> f:fs)) (intercalate " . " (take (length fs + 1) vs) ++ " <$> x"), Delete Stmt (toSSA q)]]
   where
     isSimple (fromApps -> xs) = all isAtom (x : xs)
     vs = ('f':) . show <$> [0..]
@@ -229,7 +269,6 @@
     notDol :: LHsExpr GhcPs -> Bool
     notDol (L _ (OpApp _ _ op _)) = not $ isDol op
     notDol _ = True
-
 monadStep _ _ = []
 
 -- Suggest removing a return
@@ -241,28 +280,28 @@
 monadLet :: [ExprLStmt GhcPs] -> [(ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan)]
 monadLet xs = mapMaybe mkLet xs
   where
-    vs = concatMap pvars [p | (L _ (BindStmt _ p _ _ _)) <- xs]
+    vs = concatMap pvars [p | (L _ (BindStmt _ p _ )) <- xs]
 
     mkLet :: ExprLStmt GhcPs -> Maybe (ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan)
-    mkLet x@(L _ (BindStmt _ v@(view -> PVar_ p) (fromRet -> Just (_, y)) _ _ ))
+    mkLet x@(L _ (BindStmt _ v@(view -> PVar_ p) (fromRet -> Just (_, y))))
       | p `notElem` vars y, p `notElem` delete p vs
       = Just (x, template p y, refact)
       where
-        refact = Replace Stmt (toSS x) [("lhs", toSS v), ("rhs", toSS y)]
+        refact = Replace Stmt (toSSA x) [("lhs", toSSA v), ("rhs", toSSA y)]
                       (unsafePrettyPrint $ template "lhs" (strToVar "rhs"))
     mkLet _ = Nothing
 
     template :: String -> LHsExpr GhcPs -> ExprLStmt GhcPs
     template lhs rhs =
-        let p = noLoc $ mkRdrUnqual (mkVarOcc lhs)
-            grhs = noLoc (GRHS noExtField [] rhs)
-            grhss = GRHSs noExtField [grhs] (noLoc (EmptyLocalBinds noExtField))
-            match = noLoc $ Match noExtField (FunRhs p Prefix NoSrcStrict) [] grhss
-            fb = noLoc $ FunBind noExtField p (MG noExtField (noLoc [match]) Generated) WpHole []
-            binds = unitBag fb
-            valBinds = ValBinds noExtField binds []
-            localBinds = noLoc $ HsValBinds noExtField valBinds
-         in noLoc $ LetStmt noExtField localBinds
+        let p = noLocA $ mkRdrUnqual (mkVarOcc lhs)
+            grhs = noLocA (GRHS noAnn [] rhs)
+            grhss = GRHSs emptyComments [grhs] (EmptyLocalBinds noExtField)
+            match = noLocA $ Match noExtField (FunRhs p Prefix NoSrcStrict noAnn) (noLocA []) grhss
+            fb = noLocA $ FunBind noExtField p (MG (Generated OtherExpansion SkipPmc) (noLocA [match]))
+            binds = [fb]
+            valBinds = ValBinds NoAnnSortKey binds []
+            localBinds = HsValBinds noAnn valBinds
+         in noLocA $ LetStmt noAnn localBinds
 
 fromApplies :: LHsExpr GhcPs -> ([LHsExpr GhcPs], LHsExpr GhcPs)
 fromApplies (L _ (HsApp _ f x)) = first (f:) $ fromApplies (fromParen x)
@@ -271,6 +310,6 @@
 
 fromRet :: LHsExpr GhcPs -> Maybe (String, LHsExpr GhcPs)
 fromRet (L _ (HsPar _ x)) = fromRet x
-fromRet (L _ (OpApp _ x (L _ (HsVar _ (L _ y))) z)) | occNameStr y == "$" = fromRet $ noLoc (HsApp noExtField x z)
+fromRet (L _ (OpApp _ x (L _ (HsVar _ (L _ y))) z)) | occNameStr y == "$" = fromRet $ noLocA (HsApp noExtField x z)
 fromRet (L _ (HsApp _ x y)) | isReturn x = Just (unsafePrettyPrint x, y)
 fromRet _ = Nothing
diff --git a/src/Hint/Naming.hs b/src/Hint/Naming.hs
--- a/src/Hint/Naming.hs
+++ b/src/Hint/Naming.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE ViewPatterns #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-
@@ -44,18 +45,20 @@
 import Hint.Type (Idea,DeclHint,suggest,ghcModule)
 import Data.Generics.Uniplate.DataOnly
 import Data.List.Extra (nubOrd, isPrefixOf)
+import Data.List.NonEmpty (toList)
 import Data.Data
 import Data.Char
 import Data.Maybe
-import qualified Data.Set as Set
+import Data.Set qualified as Set
 
-import BasicTypes
-import FastString
+import GHC.Types.Basic
+import GHC.Types.SourceText
+import GHC.Data.FastString
 import GHC.Hs.Decls
 import GHC.Hs.Extension
 import GHC.Hs
-import OccName
-import SrcLoc
+import GHC.Types.Name.Occurrence
+import GHC.Types.SrcLoc
 
 import Language.Haskell.GhclibParserEx.GHC.Hs.Decls
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
@@ -67,8 +70,8 @@
 naming :: Set.Set String -> LHsDecl GhcPs -> [Idea]
 naming seen originalDecl =
     [ suggest "Use camelCase"
-               (shorten originalDecl)
-               (shorten replacedDecl)
+               (reLoc (shorten originalDecl))
+               (reLoc (shorten replacedDecl))
                [ -- https://github.com/mpickering/apply-refact/issues/39
                ]
     | not $ null suggestedNames
@@ -84,33 +87,39 @@
         replacedDecl = replaceNames suggestedNames originalDecl
 
 shorten :: LHsDecl GhcPs -> LHsDecl GhcPs
-shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG _ (L locMatches matches) FromSource) _ _))) =
+shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG FromSource (L locMatches matches))))) =
     L locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = L locMatches $ map shortenMatch matches}})
-shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _) _))) =
+shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ _ grhss@(GRHSs _ rhss _)))) =
     L locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}})
 shorten x = x
 
 shortenMatch :: LMatch GhcPs (LHsExpr GhcPs) -> LMatch GhcPs (LHsExpr GhcPs)
 shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) =
     L locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}}
-shortenMatch x = x
 
 shortenLGRHS :: LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs)
 shortenLGRHS (L locGRHS (GRHS ttg0 guards (L locExpr _))) =
-    L locGRHS (GRHS ttg0 guards (cL locExpr dots))
+    L locGRHS (GRHS ttg0 guards (L locExpr dots))
     where
         dots :: HsExpr GhcPs
-        dots = HsLit noExtField (HsString (SourceText "...") (mkFastString "..."))
-shortenLGRHS x = x
+        dots = HsLit noExtField (HsString (SourceText (fsLit "...")) (fsLit "..."))
 
 getNames :: LHsDecl GhcPs -> [String]
 getNames decl = maybeToList (declName decl) ++ getConstructorNames (unLoc decl)
 
 getConstructorNames :: HsDecl GhcPs -> [String]
-getConstructorNames (TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ _ cons _))) =
-    concatMap (map unsafePrettyPrint . getConNames . unLoc) cons
-getConstructorNames _ = []
+getConstructorNames tycld = case tycld of
+    (TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ (NewTypeCon con) _))) -> conNames [con]
+    (TyClD _ (DataDecl _ _ _ _ (HsDataDefn _ _ _ _ (DataTypeCons _ cons) _))) -> conNames cons
+    _ -> []
+  where
+    conNames :: [LConDecl GhcPs] -> [String]
+    conNames =  concatMap (map unsafePrettyPrint . conNamesInDecl . unLoc)
 
+    conNamesInDecl :: ConDecl GhcPs -> [LIdP GhcPs]
+    conNamesInDecl ConDeclH98  {con_name  = name}  = [name]
+    conNamesInDecl ConDeclGADT {con_names = names} = Data.List.NonEmpty.toList names
+
 isSym :: String -> Bool
 isSym (x:_) = not $ isAlpha x || x `elem` "_'"
 isSym _ = False
@@ -118,10 +127,10 @@
 suggestName :: String -> Maybe String
 suggestName original
     | isSym original || good || not (any isLower original) || any isDigit original ||
-        any (`isPrefixOf` original) ["prop_","case_","unit_","test_","spec_","scprop_","hprop_"] = Nothing
+        any (`isPrefixOf` original) ["prop_","case_","unit_","test_","spec_","scprop_","hprop_","tasty_"] = Nothing
     | otherwise = Just $ f original
     where
-        good = all isAlphaNum $ drp '_' $ drp '#' $ filter (/= '\'') $ reverse $ drp '_' original
+        good = all isAlphaNum $ drp '_' $ drp '#' $ reverse $ filter (/= '\'') $ drp '_' original
         drp x = dropWhile (== x)
 
         f xs = us ++ g ys
diff --git a/src/Hint/Negation.hs b/src/Hint/Negation.hs
new file mode 100644
--- /dev/null
+++ b/src/Hint/Negation.hs
@@ -0,0 +1,62 @@
+{-
+
+Raise a warning if negation precedence may appear ambiguous to human readers.
+
+<TEST>
+yes = -1 ^ 2 -- @Suggestion -(1 ^ 2)
+yes = -x ^ y -- @Suggestion -(x ^ y)
+yes = -5 `plus` 3 -- @Suggestion -(5 `plus` 3)
+yes = -f x `mod` y -- @Suggestion -(f x `mod` y)
+yes = -x `mod` y -- @Suggestion -(x `mod` y)
+no = -(5 + 3)
+no = -5 + 3
+no = -(f x)
+no = -x
+</TEST>
+-}
+
+module Hint.Negation(negationParensHint) where
+
+import Hint.Type(DeclHint,Idea(..),rawIdea,toSSA)
+import Config.Type
+import Data.Generics.Uniplate.DataOnly
+import Refact.Types
+import GHC.Hs
+import GHC.Util
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import GHC.Types.SrcLoc
+
+-- | See [motivating issue #1484](https://github.com/ndmitchell/hlint/issues/1484).
+--
+-- == Implementation note
+--
+-- The original intention was to compare fixities so as
+-- to only fire the rule when the operand of prefix negation
+-- has higher fixity than the negation itself (fixity 6).
+--
+-- However, since there do not exist any numerically-valued
+-- operators with lower fixity than 6
+-- (see [table](https://www.haskell.org/onlinereport/decls.html#sect4.4.2)),
+-- we do not have to worry about fixity comparisons.
+negationParensHint :: DeclHint
+negationParensHint _ _ x =
+  concatMap negatedOp (universeBi x :: [LHsExpr GhcPs])
+
+negatedOp :: LHsExpr GhcPs -> [Idea]
+negatedOp e =
+  case e of
+    L b1 (NegApp a1 inner@(L _ OpApp {}) a2) ->
+      pure $
+        rawIdea
+          Suggestion
+          "Parenthesize unary negation"
+          (locA (getLoc e))
+          (unsafePrettyPrint e)
+          (Just renderedNewExpr)
+          []
+          [Replace (findType e) (toSSA e) [] renderedNewExpr]
+        where
+          renderedNewExpr = unsafePrettyPrint newExpr
+          parenthesizedOperand = addParen inner
+          newExpr = L b1 $ NegApp a1 parenthesizedOperand a2
+    _ -> []
diff --git a/src/Hint/NewType.hs b/src/Hint/NewType.hs
--- a/src/Hint/NewType.hs
+++ b/src/Hint/NewType.hs
@@ -12,12 +12,12 @@
 data Foo = Foo { field1, field2 :: Int}
 data S a = forall b . Show b => S b
 {-# LANGUAGE RankNTypes #-}; data S a = forall b . Show b => S b
-{-# LANGUAGE RankNTypes #-}; data Foo = Foo (forall a. a) -- newtype Foo = Foo (forall a. a)
+{-# LANGUAGE RankNTypes #-}; data Foo = Foo (forall a . a) -- newtype Foo = Foo (forall a. a)
 data Color a = Red a | Green a | Blue a
 data Pair a b = Pair a b
 data Foo = Bar
 data Foo a = Eq a => MkFoo a
-data Foo a = () => Foo a -- newtype Foo a = Foo a
+data Foo a = () => Foo a -- newtype Foo a = () => Foo a
 data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int
 data A = A {b :: !C} -- newtype A = A {b :: C}
 data A = A Int#
@@ -46,7 +46,7 @@
 import Data.List (isSuffixOf)
 import GHC.Hs.Decls
 import GHC.Hs
-import SrcLoc
+import GHC.Types.SrcLoc
 import Data.Generics.Uniplate.Data
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
@@ -56,15 +56,15 @@
 newtypeHintDecl :: LHsDecl GhcPs -> [Idea]
 newtypeHintDecl old
     | Just WarnNewtype{newDecl, insideType} <- singleSimpleField old
-    = [(suggestN "Use newtype instead of data" old newDecl)
+    = [(suggestN "Use newtype instead of data" (reLoc old) (reLoc newDecl))
             {ideaNote = [DecreasesLaziness | warnBang insideType]}]
 newtypeHintDecl _ = []
 
 newTypeDerivingStrategiesHintDecl :: LHsDecl GhcPs -> [Idea]
 newTypeDerivingStrategiesHintDecl decl@(L _ (TyClD _ (DataDecl _ _ _ _ dataDef))) =
-    [ignoreNoSuggestion "Use DerivingStrategies" decl | shouldSuggestStrategies dataDef]
-newTypeDerivingStrategiesHintDecl decl@(L _ (InstD _ (DataFamInstD _ (DataFamInstDecl (HsIB _ (FamEqn _ _ _ _ _ dataDef)))))) =
-    [ignoreNoSuggestion "Use DerivingStrategies" decl | shouldSuggestStrategies dataDef]
+    [ignoreNoSuggestion "Use DerivingStrategies" (reLoc decl) | shouldSuggestStrategies dataDef]
+newTypeDerivingStrategiesHintDecl decl@(L _ (InstD _ (DataFamInstD _ (DataFamInstDecl ((FamEqn _ _ _ _ _ dataDef)))))) =
+    [ignoreNoSuggestion "Use DerivingStrategies" (reLoc decl) | shouldSuggestStrategies dataDef]
 newTypeDerivingStrategiesHintDecl _ = []
 
 -- | Determine if the given data definition should use deriving strategies.
@@ -72,13 +72,11 @@
 shouldSuggestStrategies dataDef = not (isData dataDef) && not (hasAllStrategies dataDef)
 
 hasAllStrategies :: HsDataDefn GhcPs -> Bool
-hasAllStrategies (HsDataDefn _ NewType _ _ _ _ (L _ xs)) = all hasStrategyClause xs
-hasAllStrategies _ = False
+hasAllStrategies (HsDataDefn _ _ _ _ _  xs) = all hasStrategyClause xs
 
 isData :: HsDataDefn GhcPs -> Bool
-isData (HsDataDefn _ NewType _ _ _ _ _) = False
-isData (HsDataDefn _ DataType _ _ _ _ _) = True
-isData _ = False
+isData (HsDataDefn _ _ _ _ (NewTypeCon _) _) = False
+isData (HsDataDefn _ _ _ _ (DataTypeCons _ _) _) = True
 
 hasStrategyClause :: LHsDerivingClause GhcPs -> Bool
 hasStrategyClause (L _ (HsDerivingClause _ (Just _) _)) = True
@@ -99,37 +97,45 @@
 singleSimpleField :: LHsDecl GhcPs -> Maybe WarnNewtype
 singleSimpleField (L loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef)))
     | Just inType <- simpleHsDataDefn dataDef =
-        Just WarnNewtype
+        case dropBangs dataDef of
+          DataTypeCons False [con] ->
+            Just WarnNewtype
               { newDecl = L loc $ TyClD ext decl {tcdDataDefn = dataDef
-                  { dd_ND = NewType
-                  , dd_cons = dropBangs dataDef
-                  }}
+                  { dd_cons = NewTypeCon con }}
               , insideType = inType
               }
-singleSimpleField (L loc (InstD ext (DataFamInstD instExt (DataFamInstDecl (HsIB hsibExt famEqn@(FamEqn _ _ _ _ _ dataDef))))))
+          DataTypeCons True [_] -> Nothing -- Extension "TypeData": `type data T x = ...`
+          _ -> Nothing
+singleSimpleField (L loc (InstD ext (DataFamInstD instExt (DataFamInstDecl famEqn@(FamEqn _ _ _ _ _ dataDef)))))
     | Just inType <- simpleHsDataDefn dataDef =
-        Just WarnNewtype
-          { newDecl = L loc $ InstD ext $ DataFamInstD instExt $ DataFamInstDecl $ HsIB hsibExt famEqn {feqn_rhs = dataDef
-                  { dd_ND = NewType
-                  , dd_cons = dropBangs dataDef
-                  }}
+        case dropBangs dataDef of
+          DataTypeCons False [con] ->
+            Just WarnNewtype
+              { newDecl = L loc $ InstD ext $ DataFamInstD instExt $ DataFamInstDecl $ famEqn {feqn_rhs = dataDef
+                      { dd_cons = NewTypeCon con }}
               , insideType = inType
               }
+          DataTypeCons True [_] -> Nothing --  -- Extension "TypeData": `type data T x = ...`
+          _ -> Nothing
 singleSimpleField _ = Nothing
 
-dropBangs :: HsDataDefn GhcPs -> [LConDecl GhcPs]
-dropBangs = map (fmap dropConsBang) . dd_cons
+dropBangs :: HsDataDefn GhcPs -> DataDefnCons (LConDecl GhcPs)
+dropBangs def =
+  case dd_cons def of
+    NewTypeCon a -> NewTypeCon (dropConsBang <$> a)
+    DataTypeCons isTypeData as -> DataTypeCons isTypeData (map (dropConsBang <$>) as)
 
--- | Checks whether its argument is a \"simple\" data definition (see 'singleSimpleField')
--- returning the type inside its constructor if it is.
+-- | Checks whether its argument is a \"simple\" data definition (see
+-- 'singleSimpleField') returning the single thing under its
+-- constructor if it is.
 simpleHsDataDefn :: HsDataDefn GhcPs -> Maybe (HsType GhcPs)
-simpleHsDataDefn (HsDataDefn _ DataType _ _ _ [L _ constructor] _) = simpleCons constructor
+simpleHsDataDefn (HsDataDefn _ _ _ _ (DataTypeCons _ [L _ constructor]) _) = simpleCons constructor
 simpleHsDataDefn _ = Nothing
 
 -- | Checks whether its argument is a \"simple\" constructor (see criteria in 'singleSimpleField')
 -- returning the type inside the constructor if it is. This is needed for strictness analysis.
 simpleCons :: ConDecl GhcPs -> Maybe (HsType GhcPs)
-simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [L _ inType]) _)
+simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [] [HsScaled _ (L _ inType)]) _)
     | emptyOrNoContext context
     , not $ isUnboxedTuple inType
     , not $ isHashy inType
@@ -145,7 +151,7 @@
 isHashy x = or ["#" `isSuffixOf` unsafePrettyPrint v | v@HsTyVar{} <- universe x]
 
 warnBang :: HsType GhcPs -> Bool
-warnBang (HsBangTy _ (HsSrcBang _ _ SrcStrict) _) = False
+warnBang (HsBangTy _ (HsBang _ SrcStrict) _) = False
 warnBang _ = True
 
 emptyOrNoContext :: Maybe (LHsContext GhcPs) -> Bool
@@ -155,10 +161,13 @@
 
 -- | The \"Bang\" here refers to 'HsSrcBang', which notably also includes @UNPACK@ pragmas!
 dropConsBang :: ConDecl GhcPs -> ConDecl GhcPs
-dropConsBang decl@(ConDeclH98 _ _ _ _ _ (PrefixCon fields) _) =
-    decl {con_args = PrefixCon $ map getBangType fields}
+-- fields [HsScaled GhcPs (LBangType GhcPs)]
+dropConsBang decl@(ConDeclH98 _ _ _ _ _ (PrefixCon [] fields) _) =
+    -- decl {con_args = PrefixCon $ map getBangType fields}
+    let fs' = map (\(HsScaled s lt) -> HsScaled s (getBangType lt)) fields  :: [HsScaled GhcPs (LBangType GhcPs)]
+    in decl {con_args = PrefixCon [] fs'}
 dropConsBang decl@(ConDeclH98 _ _ _ _ _ (RecCon (L recloc conDeclFields)) _) =
-    decl {con_args = RecCon $ cL recloc $ removeUnpacksRecords conDeclFields}
+    decl {con_args = RecCon $ L recloc $ removeUnpacksRecords conDeclFields}
     where
         removeUnpacksRecords :: [LConDeclField GhcPs] -> [LConDeclField GhcPs]
         removeUnpacksRecords = map (\(L conDeclFieldLoc x) -> L conDeclFieldLoc $ removeConDeclFieldUnpacks x)
@@ -166,7 +175,6 @@
         removeConDeclFieldUnpacks :: ConDeclField GhcPs -> ConDeclField GhcPs
         removeConDeclFieldUnpacks conDeclField@(ConDeclField _ _ fieldType _) =
             conDeclField {cd_fld_type = getBangType fieldType}
-        removeConDeclFieldUnpacks x = x
 dropConsBang x = x
 
 isUnboxedTuple :: HsType GhcPs -> Bool
diff --git a/src/Hint/NumLiteral.hs b/src/Hint/NumLiteral.hs
new file mode 100644
--- /dev/null
+++ b/src/Hint/NumLiteral.hs
@@ -0,0 +1,119 @@
+{-
+    Suggest the usage of underscore when NumericUnderscores is enabled.
+
+<TEST>
+123456
+{-# LANGUAGE NumericUnderscores #-} \
+1234
+{-# LANGUAGE NumericUnderscores #-} \
+12345 -- @Suggestion 12_345 @NoRefactor
+{-# LANGUAGE NumericUnderscores #-} \
+123456789.0441234e-123456 -- @Suggestion 123_456_789.044_123_4e-123_456 @NoRefactor
+{-# LANGUAGE NumericUnderscores #-} \
+0x12abc.523defp+172345 -- @Suggestion 0x1_2abc.523d_efp+172_345 @NoRefactor
+{-# LANGUAGE NumericUnderscores #-} \
+3.14159265359 -- @Suggestion 3.141_592_653_59 @NoRefactor
+{-# LANGUAGE NumericUnderscores #-} \
+12_33574_56
+</TEST>
+
+-}
+
+module Hint.NumLiteral (numLiteralHint) where
+
+import GHC.Hs
+import GHC.Data.FastString
+import GHC.LanguageExtensions.Type (Extension (..))
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
+import GHC.Util.ApiAnnotation (extensions)
+import Data.Char (isDigit, isOctDigit, isHexDigit)
+import Data.List (intercalate)
+import Data.Set (union)
+import Data.Generics.Uniplate.DataOnly (universeBi)
+import Refact.Types
+
+import Hint.Type (DeclHint, toSSA, modComments, firstDeclComments)
+import Idea (Idea, suggest)
+
+numLiteralHint :: DeclHint
+numLiteralHint _ modu =
+  -- Comments appearing without an empty line before the first
+  -- declaration in a module are now associated with the declaration
+  -- not the module so to be safe, look also at `firstDeclComments
+  -- modu` (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
+  let exts = union (extensions (modComments modu)) (extensions (firstDeclComments modu)) in
+  if NumericUnderscores `elem` exts then
+     concatMap suggestUnderscore . universeBi
+  else
+     const []
+
+suggestUnderscore :: LHsExpr GhcPs -> [Idea]
+suggestUnderscore x@(L _ (HsOverLit _ ol@(OverLit _ (HsIntegral intLit@(IL (SourceText srcTxt) _ _))))) =
+  [ suggest "Use underscore" (reLoc x) (reLoc y) [r] | '_' `notElem` unpackFS srcTxt, unpackFS srcTxt /= underscoredSrcTxt ]
+  where
+    underscoredSrcTxt = addUnderscore (unpackFS srcTxt)
+    y :: LocatedAn NoEpAnns (HsExpr GhcPs)
+    y = noLocA $ HsOverLit noExtField $ ol{ol_val = HsIntegral intLit{il_text = SourceText (fsLit underscoredSrcTxt)}}
+    r = Replace Expr (toSSA x) [("a", toSSA y)] "a"
+suggestUnderscore x@(L _ (HsOverLit _ ol@(OverLit _ (HsFractional fracLit@(FL (SourceText srcTxt) _ _ _ _))))) =
+  [ suggest "Use underscore" (reLoc x) (reLoc y) [r] | '_' `notElem` unpackFS srcTxt, unpackFS srcTxt /= underscoredSrcTxt ]
+  where
+    underscoredSrcTxt = addUnderscore (unpackFS srcTxt)
+    y :: LocatedAn NoEpAnns (HsExpr GhcPs)
+    y = noLocA $ HsOverLit noExtField $ ol{ol_val = HsFractional fracLit{fl_text = SourceText (fsLit underscoredSrcTxt)}}
+    r = Replace Expr (toSSA x) [("a", toSSA y)] "a"
+suggestUnderscore _ = mempty
+
+addUnderscore :: String -> String
+addUnderscore intStr = numLitToStr underscoredNumLit
+ where
+   numLit = toNumLiteral intStr
+   underscoredNumLit = numLit{ nl_intPart = underscoreFromRight chunkSize $ nl_intPart numLit
+                             , nl_fracPart = underscore chunkSize $ nl_fracPart numLit
+                             , nl_exp = underscoreFromRight 3 $ nl_exp numLit -- Exponential part is always decimal
+                             }
+   chunkSize = if null (nl_prefix numLit) then 3 else 4
+
+   underscore chunkSize = intercalate "_" . chunk chunkSize
+   underscoreFromRight chunkSize str
+     | length str < 5 = str
+     | otherwise = reverse . underscore chunkSize . reverse $ str
+   chunk chunkSize [] = []
+   chunk chunkSize xs = a:chunk chunkSize b where (a, b) = splitAt chunkSize xs
+
+data NumLiteral = NumLiteral
+  { nl_prefix :: String
+  , nl_intPart :: String
+  , nl_decSep :: String -- decimal separator
+  , nl_fracPart :: String
+  , nl_expSep :: String -- e, e+, e-, p, p+, p-
+  , nl_exp :: String
+  } deriving (Show, Eq)
+
+toNumLiteral :: String -> NumLiteral
+toNumLiteral str = case str of
+  '0':'b':digits -> (afterPrefix isBinDigit digits){nl_prefix = "0b"}
+  '0':'B':digits -> (afterPrefix isBinDigit digits){nl_prefix = "0B"}
+  '0':'o':digits -> (afterPrefix isOctDigit digits){nl_prefix = "0o"}
+  '0':'O':digits -> (afterPrefix isOctDigit digits){nl_prefix = "0O"}
+  '0':'x':digits -> (afterPrefix isHexDigit digits){nl_prefix = "0x"}
+  '0':'X':digits -> (afterPrefix isHexDigit digits){nl_prefix = "0X"}
+  _              -> afterPrefix isDigit str
+  where
+    isBinDigit x = x == '0' || x == '1'
+
+    afterPrefix isDigit str = (afterIntPart isDigit suffix){nl_intPart = intPart}
+      where (intPart, suffix) = span isDigit str
+
+    afterIntPart isDigit ('.':suffix) = (afterDecSep isDigit suffix){nl_decSep = "."}
+    afterIntPart isDigit str = afterFracPart str
+
+    afterDecSep isDigit str = (afterFracPart suffix){nl_fracPart = fracPart}
+      where (fracPart, suffix) = span isDigit str
+
+    afterFracPart str = NumLiteral "" "" "" "" expSep exp
+      where (expSep, exp) = break isDigit str
+
+numLitToStr :: NumLiteral -> String
+numLitToStr (NumLiteral p ip ds fp es e) = p ++ ip ++ ds ++ fp ++ es ++ e
diff --git a/src/Hint/Pattern.hs b/src/Hint/Pattern.hs
--- a/src/Hint/Pattern.hs
+++ b/src/Hint/Pattern.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE ViewPatterns, PatternGuards, TypeFamilies #-}
 
 {-
@@ -58,7 +59,7 @@
 
 module Hint.Pattern(patternHint) where
 
-import Hint.Type(DeclHint,Idea,ghcAnnotations,ideaTo,toSS,toRefactSrcSpan,suggest,suggestRemove,warn)
+import Hint.Type(DeclHint,Idea,modComments,firstDeclComments,ideaTo,toSSA,toRefactSrcSpan,suggest,suggestRemove,warn)
 import Data.Generics.Uniplate.DataOnly
 import Data.Function
 import Data.List.Extra
@@ -66,14 +67,14 @@
 import Data.Maybe
 import Data.Either
 import Refact.Types hiding (RType(Pattern, Match), SrcSpan)
-import qualified Refact.Types as R (RType(Pattern, Match), SrcSpan)
+import Refact.Types qualified as R (RType(Pattern, Match), SrcSpan)
 
-import GHC.Hs
-import SrcLoc
-import RdrName
-import OccName
-import Bag
-import BasicTypes
+import GHC.Hs hiding(asPattern)
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
+import GHC.Types.Basic hiding (Pattern)
+import GHC.Data.Strict qualified
 
 import GHC.Util
 import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
@@ -90,11 +91,16 @@
     concatMap (patHint strict True) (universeBi $ transformBi noPatBind x) ++
     concatMap expHint (universeBi x)
   where
-    exts = nubOrd $ concatMap snd (languagePragmas (pragmas (ghcAnnotations modu))) -- language extensions enabled at source
+    -- Comments appearing without an empty line before the first
+    -- declaration in a module are now associated with the declaration
+    -- not the module so to be safe, look also at `firstDeclComments
+    -- modu`
+    -- (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
+    exts = nubOrd $ concatMap snd (languagePragmas (pragmas (modComments modu) ++ pragmas (firstDeclComments modu))) -- language extensions enabled at source
     strict = "Strict" `elem` exts
 
     noPatBind :: LHsBind GhcPs -> LHsBind GhcPs
-    noPatBind (L loc a@PatBind{}) = L loc a{pat_lhs=noLoc (WildPat noExtField)}
+    noPatBind (L loc a@PatBind{}) = L loc a{pat_lhs=noLocA (WildPat noExtField)}
     noPatBind x = x
 
 {-
@@ -111,16 +117,16 @@
 
 hints :: (String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) -> Pattern -> [Idea]
 hints gen (Pattern l rtype pat (GRHSs _ [L _ (GRHS _ [] bod)] bind))
-  | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GRHSs noExtField guards bind)) [refactoring]]
+  | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GRHSs emptyComments guards bind)) [refactoring]]
   where
     rawGuards :: [(LHsExpr GhcPs, LHsExpr GhcPs)]
     rawGuards = asGuards bod
 
     mkGuard :: LHsExpr GhcPs -> (LHsExpr GhcPs -> GRHS GhcPs (LHsExpr GhcPs))
-    mkGuard a = GRHS noExtField [noLoc $ BodyStmt noExtField a noSyntaxExpr noSyntaxExpr]
+    mkGuard a = GRHS noAnn [noLocA $ BodyStmt noExtField a noSyntaxExpr noSyntaxExpr]
 
     guards :: [LGRHS GhcPs (LHsExpr GhcPs)]
-    guards = map (noLoc . uncurry mkGuard) rawGuards
+    guards = map (noLocA . uncurry mkGuard) rawGuards
 
     (lhs, rhs) = unzip rawGuards
 
@@ -128,7 +134,7 @@
       -- Check if the expression has been injected or is natural.
       zipWith checkLoc ps ['1' .. '9']
       where
-        checkLoc p@(L l _) v = if l == noSrcSpan then Left p else Right (c ++ [v], toSS p)
+        checkLoc p@(L l _) v = if locA l == noSrcSpan then Left p else Right (c ++ [v], toSSA p)
 
     patSubts =
       case pat of
@@ -137,43 +143,43 @@
         ps  -> mkTemplate "p100" ps
     guardSubts = mkTemplate "g100" lhs
     exprSubts  = mkTemplate "e100" rhs
-    templateGuards = map noLoc (zipWith (mkGuard `on` toString) guardSubts exprSubts)
+    templateGuards = map noLocA (zipWith (mkGuard `on` toString) guardSubts exprSubts)
 
     toString (Left e) = e
     toString (Right (v, _)) = strToVar v
     toString' (Left e) = e
     toString' (Right (v, _)) = strToPat v
 
-    template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString' patSubts) (GRHSs noExtField templateGuards bind)) [])
+    template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString' patSubts) (GRHSs emptyComments templateGuards bind)) [])
 
     f :: [Either a (String, R.SrcSpan)] -> [(String, R.SrcSpan)]
     f = rights
     refactoring = Replace rtype (toRefactSrcSpan l) (f patSubts ++ f guardSubts ++ f exprSubts) template
 hints gen (Pattern l t pats o@(GRHSs _ [L _ (GRHS _ [test] bod)] bind))
   | unsafePrettyPrint test `elem` ["otherwise", "True"]
-  = [gen "Redundant guard" (Pattern l t pats o{grhssGRHSs=[noLoc (GRHS noExtField [] bod)]}) [Delete Stmt (toSS test)]]
+  = [gen "Redundant guard" (Pattern l t pats o{grhssGRHSs=[noLocA (GRHS noAnn [] bod)]}) [Delete Stmt (toSSA test)]]
 hints _ (Pattern l t pats bod@(GRHSs _ _ binds)) | f binds
   = [suggestRemove "Redundant where" whereSpan "where" [ {- TODO refactoring for redundant where -} ]]
   where
-    f :: LHsLocalBinds GhcPs -> Bool
-    f (L _ (HsValBinds _ (ValBinds _ bag _))) = isEmptyBag bag
-    f (L _ (HsIPBinds _ (IPBinds _ l))) = null l
+    f :: HsLocalBinds GhcPs -> Bool
+    f (HsValBinds _ (ValBinds _ l _)) = null l
+    f (HsIPBinds _ (IPBinds _ l)) = null l
     f _ = False
     whereSpan = case l of
       UnhelpfulSpan s -> UnhelpfulSpan s
-      RealSrcSpan s ->
+      RealSrcSpan s _ ->
         let end = realSrcSpanEnd s
             start = mkRealSrcLoc (srcSpanFile s) (srcLocLine end) (srcLocCol end - 5)
-         in RealSrcSpan (mkRealSrcSpan start end)
+         in RealSrcSpan (mkRealSrcSpan start end) GHC.Data.Strict.Nothing
 hints gen (Pattern l t pats o@(GRHSs _ (unsnoc -> Just (gs, L _ (GRHS _ [test] bod))) binds))
   | unsafePrettyPrint test == "True"
-  = let otherwise_ = noLoc $ BodyStmt noExtField (strToVar "otherwise") noSyntaxExpr noSyntaxExpr in
-      [gen "Use otherwise" (Pattern l t pats o{grhssGRHSs = gs ++ [noLoc (GRHS noExtField [otherwise_] bod)]}) [Replace Expr (toSS test) [] "otherwise"]]
+  = let otherwise_ = noLocA $ BodyStmt noExtField (strToVar "otherwise") noSyntaxExpr noSyntaxExpr in
+      [gen "Use otherwise" (Pattern l t pats o{grhssGRHSs = gs ++ [noLocA (GRHS noAnn [otherwise_] bod)]}) [Replace Expr (toSSA test) [] "otherwise"]]
 hints _ _ = []
 
 asGuards :: LHsExpr GhcPs -> [(LHsExpr GhcPs, LHsExpr GhcPs)]
 asGuards (L _ (HsPar _ x)) = asGuards x
-asGuards (L _ (HsIf _ _ a b c)) = (a, b) : asGuards c
+asGuards (L _ (HsIf _ a b c)) = (a, b) : asGuards c
 asGuards x = [(strToVar "otherwise", x)]
 
 data Pattern = Pattern SrcSpan R.RType [LPat GhcPs] (GRHSs GhcPs (LHsExpr GhcPs))
@@ -183,42 +189,41 @@
 asPattern (L loc x) = concatMap decl (universeBi x)
   where
     decl :: HsBind GhcPs -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)]
-    decl o@(PatBind _ pat rhs _) = [(Pattern loc Bind [pat] rhs, \msg (Pattern _ _ [pat] rhs) rs -> suggest msg (L loc o :: LHsBind GhcPs) (noLoc (PatBind noExtField pat rhs ([], [])) :: LHsBind GhcPs) rs)]
-    decl (FunBind _ _ (MG _ (L _ xs) _) _ _) = map match xs
+    decl o@(PatBind _ pat mult rhs) = [(Pattern (locA loc) Bind [pat] rhs, \msg (Pattern _ _ [pat] rhs) rs -> suggest msg (noLoc o :: Located (HsBind GhcPs)) (noLoc (PatBind noExtField pat mult rhs) :: Located (HsBind GhcPs)) rs)]
+    decl (FunBind _ _ (MG _ (L _ xs))) = map match xs
     decl _ = []
 
     match :: LMatch GhcPs (LHsExpr GhcPs) -> (Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)
-    match o@(L loc (Match _ ctx pats grhss)) = (Pattern loc R.Match pats grhss, \msg (Pattern _ _ pats grhss) rs -> suggest msg o (noLoc (Match noExtField ctx  pats grhss) :: LMatch GhcPs (LHsExpr GhcPs)) rs)
-    match _ = undefined -- {-# COMPLETE L #-}
+    match o@(L loc (Match _ ctx (L lpats pats) grhss)) = (Pattern (locA loc) R.Match pats grhss, \msg (Pattern _ _ pats grhss) rs -> suggest msg (reLoc o) (noLoc (Match noExtField ctx  (L lpats pats) grhss) :: Located (Match GhcPs (LHsExpr GhcPs))) rs)
 
 -- First Bool is if 'Strict' is a language extension. Second Bool is
 -- if this pattern in this context is going to be evaluated strictly.
 patHint :: Bool -> Bool -> LPat GhcPs -> [Idea]
-patHint _ _ o@(L _ (ConPatIn name (PrefixCon args)))
+patHint _ _ o@(L _ (ConPat _ name (PrefixCon _ args)))
   | length args >= 3 && all isPWildcard args =
-  let rec_fields = HsRecFields [] Nothing :: HsRecFields GhcPs (LPat GhcPs)
-      new        = noLoc $ ConPatIn name (RecCon rec_fields) :: LPat GhcPs
+  let rec_fields = HsRecFields noExtField [] Nothing :: HsRecFields GhcPs (LPat GhcPs)
+      new        = noLocA $ ConPat noAnn name (RecCon rec_fields) :: LPat GhcPs
   in
-  [suggest "Use record patterns" o new [Replace R.Pattern (toSS o) [] (unsafePrettyPrint new)]]
+  [suggest "Use record patterns" (reLoc o) (reLoc new) [Replace R.Pattern (toSSA o) [] (unsafePrettyPrint new)]]
 patHint _ _ o@(L _ (VarPat _ (L _ name)))
   | occNameString (rdrNameOcc name) == "otherwise" =
-    [warn "Used otherwise as a pattern" o (noLoc (WildPat noExtField) :: LPat GhcPs) []]
+    [warn "Used otherwise as a pattern" (reLoc o) (noLoc (WildPat noExtField) :: Located (Pat GhcPs)) []]
 patHint lang strict o@(L _ (BangPat _ pat@(L _ x)))
-  | strict, f x = [warn "Redundant bang pattern" o (noLoc x :: LPat GhcPs) [r]]
+  | strict, f x = [warn "Redundant bang pattern" (reLoc o) (noLoc x :: Located (Pat GhcPs)) [r]]
   where
     f :: Pat GhcPs -> Bool
     f (ParPat _ (L _ x)) = f x
     f (AsPat _ _ (L _ x)) = f x
     f LitPat {} = True
     f NPat {} = True
-    f ConPatIn {} = True
+    f ConPat {} = True
     f TuplePat {} = True
     f ListPat {} = True
     f (SigPat _ (L _ p) _) = f p
     f _ = False
-    r = Replace R.Pattern (toSS o) [("x", toSS pat)] "x"
+    r = Replace R.Pattern (toSSA o) [("x", toSSA pat)] "x"
 patHint False _ o@(L _ (LazyPat _ pat@(L _ x)))
-  | f x = [warn "Redundant irrefutable pattern" o (noLoc x :: LPat GhcPs) [r]]
+  | f x = [warn "Redundant irrefutable pattern" (reLoc o) (noLoc x :: Located (Pat GhcPs)) [r]]
   where
     f :: Pat GhcPs -> Bool
     f (ParPat _ (L _ x)) = f x
@@ -226,20 +231,20 @@
     f WildPat{} = True
     f VarPat{} = True
     f _ = False
-    r = Replace R.Pattern (toSS o) [("x", toSS pat)] "x"
+    r = Replace R.Pattern (toSSA o) [("x", toSSA pat)] "x"
 patHint _ _ o@(L _ (AsPat _ v (L _ (WildPat _)))) =
-  [warn "Redundant as-pattern" o v [Replace R.Pattern (toSS o) [] (rdrNameStr v)]]
+  [warn "Redundant as-pattern" (reLoc o) (reLoc v) [Replace R.Pattern (toSSA o) [] (rdrNameStr v)]]
 patHint _ _ _ = []
 
 expHint :: LHsExpr GhcPs -> [Idea]
  -- Note the 'FromSource' in these equations (don't warn on generated match groups).
-expHint o@(L _ (HsCase _ _ (MG _ (L _ [L _ (Match _ CaseAlt [L _ (WildPat _)] (GRHSs _ [L _ (GRHS _ [] e)] (L  _ (EmptyLocalBinds _)))) ]) FromSource ))) =
-  [suggest "Redundant case" o e [r]]
+expHint o@(L _ (HsCase _ _ (MG FromSource (L _ [L _ (Match _ CaseAlt (L _ [L _ (WildPat _)]) (GRHSs _ [L _ (GRHS _ [] e)] (EmptyLocalBinds _))) ])))) =
+  [suggest "Redundant case" (reLoc o) (reLoc e) [r]]
   where
-    r = Replace Expr (toSS o) [("x", toSS e)] "x"
-expHint o@(L _ (HsCase _ (L _ (HsVar _ (L _ x))) (MG _ (L _ [L _ (Match _ CaseAlt [L _ (VarPat _ (L _ y))] (GRHSs _ [L _ (GRHS _ [] e)] (L  _ (EmptyLocalBinds _)))) ]) FromSource )))
+    r = Replace Expr (toSSA o) [("x", toSSA e)] "x"
+expHint o@(L _ (HsCase _ (L _ (HsVar _ (L _ x))) (MG FromSource (L _ [L _ (Match _ CaseAlt (L _ [L _ (VarPat _ (L _ y))]) (GRHSs _ [L _ (GRHS _ [] e)] (EmptyLocalBinds _))) ]))))
   | occNameStr x == occNameStr y =
-      [suggest "Redundant case" o e [r]]
+      [suggest "Redundant case" (reLoc o) (reLoc e) [r]]
   where
-    r = Replace Expr (toSS o) [("x", toSS e)] "x"
+    r = Replace Expr (toSSA o) [("x", toSSA e)] "x"
 expHint _ = []
diff --git a/src/Hint/Pragma.hs b/src/Hint/Pragma.hs
--- a/src/Hint/Pragma.hs
+++ b/src/Hint/Pragma.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE ViewPatterns #-}
 {-# LANGUAGE FlexibleContexts #-}
 
@@ -25,35 +26,40 @@
 {-# LANGUAGE RebindableSyntax #-} \
 {-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE RebindableSyntax #-} \
-{-# LANGUAGE EmptyCase, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase #-}
+{-# LANGUAGE EmptyCase, RebindableSyntax #-} -- {-# LANGUAGE EmptyCase, RebindableSyntax #-}
 </TEST>
 -}
 
 
 module Hint.Pragma(pragmaHint) where
 
-import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),toSS,rawIdea)
+import Hint.Type(ModuHint,Idea(..),Severity(..),toSSAnc,rawIdea,modComments,firstDeclComments)
 import Data.List.Extra
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe
 import Refact.Types
-import qualified Refact.Types as R
+import Refact.Types qualified as R
 
-import ApiAnnotation
-import SrcLoc
+import GHC.Hs
+import GHC.Types.SrcLoc
+import GHC.Data.FastString
 
 import GHC.Util
-import DynFlags
+import GHC.Driver.Session
 
 pragmaHint :: ModuHint
 pragmaHint _ modu =
-  let ps = pragmas (ghcAnnotations modu)
+  -- Comments appearing without a line-break before the first
+  -- declaration in a module are now associated with the declaration
+  -- not the module so to be safe, look also at `firstDeclComments
+  -- modu` (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
+  let ps = pragmas (modComments modu) ++ pragmas (firstDeclComments modu)
       opts = flags ps
       lang = languagePragmas ps in
     languageDupes lang ++ optToPragma opts lang
 
-optToPragma :: [(Located AnnotationComment, [String])]
-             -> [(Located AnnotationComment, [String])]
+optToPragma :: [(LEpaComment, [String])]
+             -> [(LEpaComment, [String])]
              -> [Idea]
 optToPragma flags languagePragmas =
   [pragmaIdea (OptionsToComment (fst <$> old2) ys rs) | Just old2 <- [NE.nonEmpty old]]
@@ -66,40 +72,44 @@
       ls = concatMap snd languagePragmas
       ns2 = nubOrd (concat ns) \\ ls
 
-      ys = [mkLanguagePragmas noSrcSpan ns2 | ns2 /= []] ++ catMaybes new
-      mkRefact :: (Located AnnotationComment, [String])
-               -> Maybe (Located AnnotationComment)
+      dummyLoc = mkRealSrcLoc (fsLit "dummy") 1 1
+      dummySpan = mkRealSrcSpan dummyLoc dummyLoc
+      dummyAnchor = realSpanAsAnchor dummySpan
+
+      ys = [mkLanguagePragmas dummyAnchor ns2 | ns2 /= []] ++ catMaybes new
+      mkRefact :: (LEpaComment, [String])
+               -> Maybe LEpaComment
                -> [String]
                -> Refactoring R.SrcSpan
-      mkRefact old (maybe "" comment -> new) ns =
-        let ns' = map (\n -> comment (mkLanguagePragmas noSrcSpan [n])) ns
-        in ModifyComment (toSS (fst old)) (intercalate "\n" (filter (not . null) (ns' `snoc` new)))
+      mkRefact old (maybe "" comment_ -> new) ns =
+        let ns' = map (\n -> comment_ (mkLanguagePragmas dummyAnchor [n])) ns
+        in ModifyComment (toSSAnc (fst old)) (intercalate "\n" (filter (not . null) (ns' `snoc` new)))
 
-data PragmaIdea = SingleComment (Located AnnotationComment) (Located AnnotationComment)
-                 | MultiComment (Located AnnotationComment) (Located AnnotationComment) (Located AnnotationComment)
-                 | OptionsToComment (NE.NonEmpty (Located AnnotationComment)) [Located AnnotationComment] [Refactoring R.SrcSpan]
+data PragmaIdea = SingleComment LEpaComment LEpaComment
+                 | MultiComment LEpaComment LEpaComment LEpaComment
+                 | OptionsToComment (NE.NonEmpty LEpaComment) [LEpaComment] [Refactoring R.SrcSpan]
 
 pragmaIdea :: PragmaIdea -> Idea
 pragmaIdea pidea =
   case pidea of
     SingleComment old new ->
-      mkFewer (getLoc old) (comment old) (Just $ comment new) []
-      [ModifyComment (toSS old) (comment new)]
+      mkFewer (getAncLoc old) (comment_ old) (Just $ comment_ new) []
+      [ModifyComment (toSSAnc old) (comment_ new)]
     MultiComment repl delete new ->
-      mkFewer (getLoc repl)
-        (f [repl, delete]) (Just $ comment new) []
-        [ ModifyComment (toSS repl) (comment new)
-        , ModifyComment (toSS delete) ""]
+      mkFewer (getAncLoc repl)
+        (f [repl, delete]) (Just $ comment_ new) []
+        [ ModifyComment (toSSAnc repl) (comment_ new)
+        , ModifyComment (toSSAnc delete) ""]
     OptionsToComment old new r ->
-      mkLanguage (getLoc . NE.head $ old)
+      mkLanguage (getAncLoc . NE.head $ old)
         (f $ NE.toList old) (Just $ f new) []
         r
     where
-          f = unlines . map comment
+          f = unlines . map comment_
           mkFewer = rawIdea Hint.Type.Warning "Use fewer LANGUAGE pragmas"
           mkLanguage = rawIdea Hint.Type.Warning "Use LANGUAGE pragmas"
 
-languageDupes :: [(Located AnnotationComment, [String])] -> [Idea]
+languageDupes :: [(LEpaComment, [String])] -> [Idea]
 languageDupes ( (a@(L l _), les) : cs ) =
   (if nubOrd les /= les
        then [pragmaIdea (SingleComment a (mkLanguagePragmas l $ nubOrd les))]
@@ -126,15 +136,15 @@
 -- extension enabling flags. Return that together with a list of any
 -- language extensions enabled by this pragma that are not otherwise
 -- enabled by LANGUAGE pragmas in the module.
-optToLanguage :: (Located AnnotationComment, [String])
+optToLanguage :: (LEpaComment, [String])
                -> [String]
-               -> Maybe (Maybe (Located AnnotationComment), [String])
+               -> Maybe (Maybe LEpaComment, [String])
 optToLanguage (L loc _, flags) languagePragmas
   | any isJust vs =
       -- 'ls' is a list of language features enabled by this
       -- OPTIONS_GHC pragma that are not enabled by LANGUAGE pragmas
       -- in this module.
-      let ls = filter (not . (`elem` languagePragmas)) (concat $ catMaybes vs) in
+      let ls = concatMap (filter (`notElem` languagePragmas)) $ catMaybes vs in
       Just (res, ls)
   where
     -- Try reinterpreting each flag as a list of language features
diff --git a/src/Hint/Restrict.hs b/src/Hint/Restrict.hs
--- a/src/Hint/Restrict.hs
+++ b/src/Hint/Restrict.hs
@@ -1,5 +1,5 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE ViewPatterns #-}
@@ -21,27 +21,30 @@
 </TEST>
 -}
 
-import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),warn,rawIdea)
+import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),warn,rawIdea,modComments,firstDeclComments)
 import Config.Type
+import Util
 
 import Data.Generics.Uniplate.DataOnly
-import qualified Data.List.NonEmpty as NonEmpty
-import qualified Data.Set as Set
-import qualified Data.Map as Map
+import Data.List.NonEmpty qualified as NonEmpty
+import Data.Set qualified as Set
+import Data.Map qualified as Map
 import Data.List.Extra
+import Data.List.NonEmpty (nonEmpty)
+import Data.Either
 import Data.Maybe
+import Data.Monoid
 import Data.Semigroup
 import Data.Tuple.Extra
 import Control.Applicative
 import Control.Monad
+import Control.Monad.Extra
 import Prelude
 
 import GHC.Hs
-import RdrName
-import ApiAnnotation
-import Module
-import SrcLoc
-import OccName
+import GHC.Types.Name.Reader
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Occurrence
 import Language.Haskell.GhclibParserEx.GHC.Hs
 import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import GHC.Util
@@ -49,8 +52,14 @@
 -- FIXME: The settings should be partially applied, but that's hard to orchestrate right now
 restrictHint :: [Setting] -> ModuHint
 restrictHint settings scope m =
-    let anns = ghcAnnotations m
-        ps   = pragmas anns
+    -- Comments appearing without an empty line before the first
+    -- declaration in a module are now associated with the declaration
+    -- not the module so to be safe, look also at `firstDeclComments
+    -- modu`
+    -- (https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9517).
+    let annsMod = modComments m
+        annsFirstDecl = firstDeclComments m
+        ps   = pragmas annsMod ++ pragmas annsFirstDecl
         opts = flags ps
         exts = languagePragmas ps in
     checkPragmas modu opts exts rOthers ++
@@ -65,13 +74,18 @@
 
 data RestrictItem = RestrictItem
     {riAs :: [String]
+    ,riAsRequired :: Alt Maybe Bool
+    ,riImportStyle :: Alt Maybe RestrictImportStyle
+    ,riQualifiedStyle :: Alt Maybe QualifiedStyle
     ,riWithin :: [(String, String)]
-    ,riBadIdents :: [String]
+    ,riRestrictIdents :: RestrictIdents
     ,riMessage :: Maybe String
     }
 
 instance Semigroup RestrictItem where
-    RestrictItem x1 x2 x3 x4 <> RestrictItem y1 y2 y3 y4 = RestrictItem (x1<>y1) (x2<>y2) (x3<>y3) (x4<>y4)
+    RestrictItem x1 x2 x3 x4 x5 x6 x7
+      <> RestrictItem y1 y2 y3 y4 y5 y6 y7
+      = RestrictItem (x1<>y1) (x2<>y2) (x3<>y3) (x4<>y4) (x5<>y5) (x6<>y6) (x7<>y7)
 
 -- Contains a map from module (Nothing if the rule is unqualified) to (within, message), so that we can
 -- distinguish functions with the same name.
@@ -97,7 +111,17 @@
 
         rOthers = Map.map f $ Map.fromListWith (++) (map (second pure) ros)
         f rs = (all restrictDefault rs
-               ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin restrictBadIdents restrictMessage) | Restrict{..} <- rs, s <- restrictName])
+               ,Map.fromListWith (<>)
+                  [(,) s RestrictItem
+                    { riAs             = restrictAs
+                    , riAsRequired     = restrictAsRequired
+                    , riImportStyle    = restrictImportStyle
+                    , riQualifiedStyle = restrictQualifiedStyle
+                    , riWithin         = restrictWithin
+                    , riRestrictIdents = restrictIdents
+                    , riMessage        = restrictMessage
+                    }
+                  | Restrict{..} <- rs, s <- restrictName])
 
 ideaMessage :: Maybe String -> Idea -> Idea
 ideaMessage (Just message) w = w{ideaNote=[Note message]}
@@ -110,60 +134,134 @@
 noteMayBreak = Note "may break the code"
 
 within :: String -> String -> [(String, String)] -> Bool
-within modu func = any (\(a,b) -> (a == modu || a == "") && (b == func || b == ""))
+within modu func = any (\(a,b) -> (a ~= modu || a == "") && (b ~= func || b == ""))
+  where (~=) = wildcardMatch
 
 ---------------------------------------------------------------------
 -- CHECKS
 
 checkPragmas :: String
-              -> [(Located AnnotationComment, [String])]
-              -> [(Located AnnotationComment, [String])]
+              -> [(LEpaComment, [String])]
+              -> [(LEpaComment, [String])]
               ->  Map.Map RestrictType (Bool, Map.Map String RestrictItem)
               -> [Idea]
 checkPragmas modu flags exts mps =
   f RestrictFlag "flags" flags ++ f RestrictExtension "extensions" exts
   where
    f tag name xs =
-     [(if null good then ideaNoTo else id) $ notes $ rawIdea Hint.Type.Warning ("Avoid restricted " ++ name) l c Nothing [] []
+     [(if null good then ideaNoTo else id) $ notes $ rawIdea Hint.Type.Warning ("Avoid restricted " ++ name) (getAncLoc l) c Nothing [] []
      | Just (def, mp) <- [Map.lookup tag mps]
-     , (L l (AnnBlockComment c), les) <- xs
+     , (l@(L _ (EpaComment (EpaBlockComment c) _)), les) <- xs
      , let (good, bad) = partition (isGood def mp) les
      , let note = maybe noteMayBreak Note . (=<<) riMessage . flip Map.lookup mp
      , let notes w = w {ideaNote=note <$> bad}
      , not $ null bad]
    isGood def mp x = maybe def (within modu "" . riWithin) $ Map.lookup x mp
 
+
+-- | Extension to GHC's 'ImportDeclQualifiedStyle', expressing @qualifiedStyle: unrestricted@,
+-- i.e. the preference of "either pre- or post-, but qualified" in a rule.
+data QualifiedPostOrPre = QualifiedPostOrPre deriving Eq
+
 checkImports :: String -> [LImportDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea]
-checkImports modu imp (def, mp) =
-    [ ideaMessage riMessage
-      $ if | not allowImport -> ideaNoTo $ warn "Avoid restricted module" i i []
-           | not allowIdent  -> ideaNoTo $ warn "Avoid restricted identifiers" i i []
-           | not allowQual   -> warn "Avoid restricted qualification" i (noLoc $ (unLoc i){ ideclAs=noLoc . mkModuleName <$> listToMaybe riAs} :: Located (ImportDecl GhcPs)) []
-           | otherwise       -> error "checkImports: unexpected case"
-    | i@(L _ ImportDecl {..}) <- imp
-    , let RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (moduleNameString (unLoc ideclName)) mp
-    , let allowImport = within modu "" riWithin
-    , let allowIdent = Set.disjoint
-                       (Set.fromList riBadIdents)
-                       (Set.fromList (maybe [] (\(b, lxs) -> if b then [] else concatMap (importListToIdents . unLoc) (unLoc lxs)) ideclHiding))
-    , let allowQual = maybe True (\x -> null riAs || moduleNameString (unLoc x) `elem` riAs) ideclAs
-    , not allowImport || not allowQual || not allowIdent
-    ]
+checkImports modu lImportDecls (def, mp) = mapMaybe getImportHint lImportDecls
+  where
+    getImportHint :: LImportDecl GhcPs -> Maybe Idea
+    getImportHint i@(L _ ImportDecl{..}) = do
+      let RestrictItem{..} = getRestrictItem def ideclName mp
+      either (Just . ideaMessage riMessage) (const Nothing) $ do
+        unless (within modu "" riWithin) $
+          Left $ ideaNoTo $ warn "Avoid restricted module" (reLoc i) (reLoc i) []
 
+        let importedIdents = Set.fromList $
+              case first (== EverythingBut) <$> ideclImportList of
+                Just (False, lxs) -> concatMap (importListToIdents . unLoc) (unLoc lxs)
+                _ -> []
+            invalidIdents = case riRestrictIdents of
+              NoRestrictIdents -> Set.empty
+              ForbidIdents badIdents -> importedIdents `Set.intersection` Set.fromList badIdents
+              OnlyIdents onlyIdents -> importedIdents `Set.difference` Set.fromList onlyIdents
+        unless (Set.null invalidIdents) $
+          Left $ ideaNoTo $ warn "Avoid restricted identifiers" (reLoc i) (reLoc i) []
+
+        let qualAllowed = case (riAs, ideclAs) of
+              ([], _) -> True
+              (_, Nothing) -> maybe True not $ getAlt riAsRequired
+              (_, Just (L _ modName)) -> moduleNameString modName `elem` riAs
+        unless qualAllowed $ do
+          let i' = noLoc $ (unLoc i){ ideclAs = noLocA . mkModuleName <$> listToMaybe riAs }
+          Left $ warn "Avoid restricted alias" (reLoc i) i' []
+
+        let (expectedQual, expectedHiding) =
+              case fromMaybe ImportStyleUnrestricted $ getAlt riImportStyle of
+                ImportStyleUnrestricted
+                  | NotQualified <- ideclQualified -> (Nothing, Nothing)
+                  | otherwise -> (Just $ second (<> " or unqualified") expectedQualStyle, Nothing)
+                ImportStyleQualified -> (Just expectedQualStyle, Nothing)
+                ImportStyleExplicitOrQualified
+                  | Just (False, _) <- first (== EverythingBut) <$> ideclImportList -> (Nothing, Nothing)
+                  | otherwise ->
+                      ( Just $ second (<> " or with an explicit import list") expectedQualStyle
+                      , Nothing )
+                ImportStyleExplicit
+                  | Just (False, _) <- first (== EverythingBut) <$> ideclImportList -> (Nothing, Nothing)
+                  | otherwise ->
+                      ( Just (Right NotQualified, "unqualified")
+                      , Just $ Just (Exactly, noLocA []) )
+                ImportStyleUnqualified -> (Just (Right NotQualified, "unqualified"), Nothing)
+            expectedQualStyle =
+              case fromMaybe QualifiedStyleUnrestricted $ getAlt riQualifiedStyle of
+                QualifiedStyleUnrestricted -> (Left QualifiedPostOrPre, "qualified")
+                QualifiedStylePost -> (Right QualifiedPost, "post-qualified")
+                QualifiedStylePre -> (Right QualifiedPre, "pre-qualified")
+            -- unless expectedQual is Nothing, it holds the Idea (hint) to ultimately emit,
+            -- except in these cases when the rule's requirements are fulfilled in-source:
+            qualIdea
+              -- the rule demands a particular importStyle, and the decl obeys exactly
+              | Just (Right ideclQualified) == (fst <$> expectedQual) = Nothing
+              -- the rule demands a QualifiedPostOrPre import, and the decl does either
+              | Just (Left QualifiedPostOrPre) == (fst <$> expectedQual)
+                && ideclQualified `elem` [QualifiedPost, QualifiedPre] = Nothing
+              -- otherwise, expectedQual gets converted into a warning below (or is Nothing)
+              | otherwise = expectedQual
+        whenJust qualIdea $ \(qual, hint) -> do
+          -- convert non-Nothing qualIdea into hlint's refactoring Idea
+          let i' = noLoc $ (unLoc i){ ideclQualified = fromRight QualifiedPre qual
+                                    , ideclImportList = fromMaybe ideclImportList expectedHiding }
+              msg = moduleNameString (unLoc ideclName) <> " should be imported " <> hint
+          Left $ warn msg (reLoc i) i' []
+
+getRestrictItem :: Bool -> LocatedA ModuleName -> Map.Map String RestrictItem -> RestrictItem
+getRestrictItem def ideclName =
+  fromMaybe (RestrictItem mempty mempty mempty mempty [("","") | def] NoRestrictIdents Nothing)
+    . lookupRestrictItem ideclName
+
+lookupRestrictItem :: LocatedA ModuleName -> Map.Map String RestrictItem -> Maybe RestrictItem
+lookupRestrictItem ideclName mp =
+    let moduleName = moduleNameString $ unLoc ideclName
+        exact = Map.lookup moduleName mp
+        wildcard = nonEmpty
+            . fmap snd
+            . reverse -- the hope is less specific matches will end up last, but it's not guaranteed
+            . filter (liftA2 (&&) (elem '*') (`wildcardMatch` moduleName) . fst)
+            $ Map.toList mp
+    in exact <> sconcat (sequence wildcard)
+
 importListToIdents :: IE GhcPs -> [String]
 importListToIdents =
   catMaybes .
-  \case (IEVar _ n)              -> [fromName n]
-        (IEThingAbs _ n)         -> [fromName n]
-        (IEThingAll _ n)         -> [fromName n]
-        (IEThingWith _ n _ ns _) -> fromName n : map fromName ns
+  \case (IEVar _ n _)              -> [fromName n]
+        (IEThingAbs _ n _)         -> [fromName n]
+        (IEThingAll _ n _)         -> [fromName n]
+        (IEThingWith _ n _ ns _)   -> fromName n : map fromName ns
         _                        -> []
   where
-    fromName :: LIEWrappedName (IdP GhcPs) -> Maybe String
-    fromName wrapped = case unLoc wrapped of
-                         IEName    n -> fromId (unLoc n)
-                         IEPattern n -> ("pattern " ++) <$> fromId (unLoc n)
-                         IEType    n -> ("type " ++) <$> fromId (unLoc n)
+    fromName :: LIEWrappedName GhcPs -> Maybe String
+    fromName wrapped =
+      case unLoc wrapped of
+        IEName    _ n -> fromId (unLoc n)
+        IEPattern _ n -> ("pattern " ++) <$> fromId (unLoc n)
+        IEType    _ n -> ("type " ++) <$> fromId (unLoc n)
 
     fromId :: IdP GhcPs -> Maybe String
     fromId (Unqual n) = Just $ occNameString n
@@ -173,10 +271,10 @@
 
 checkFunctions :: Scope -> String -> [LHsDecl GhcPs] -> RestrictFunctions -> [Idea]
 checkFunctions scope modu decls (def, mp) =
-    [ (ideaMessage message $ ideaNoTo $ warn "Avoid restricted function" x x []){ideaDecl = [dname]}
+    [ (ideaMessage message $ ideaNoTo $ warn "Avoid restricted function" (reLoc x) (reLoc x) []){ideaDecl = [dname]}
     | d <- decls
     , let dname = fromMaybe "" (declName d)
-    , x <- universeBi d :: [Located RdrName]
+    , x <- universeBi d :: [LocatedN RdrName]
     , let xMods = possModules scope x
     , let (withins, message) = fromMaybe ([("","") | def], Nothing) (findFunction mp x xMods)
     , not $ within modu dname withins
@@ -188,7 +286,7 @@
 -- withins and messages are concatenated with (<>).
 findFunction
     :: Map.Map String RestrictFunction
-    -> Located RdrName
+    -> LocatedN RdrName
     -> [ModuleName]
     -> Maybe ([(String, String)], Maybe String)
 findFunction restrictMap (rdrNameStr -> x) (map moduleNameString -> possMods) = do
diff --git a/src/Hint/Smell.hs b/src/Hint/Smell.hs
--- a/src/Hint/Smell.hs
+++ b/src/Hint/Smell.hs
@@ -1,8 +1,6 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 
-module Hint.Smell (
-  smellModuleHint,
-  smellHint
-  ) where
+module Hint.Smell (smellModuleHint,smellHint) where
 
 {-
 <TEST> [{smell: { type: many arg functions, limit: 2 }}]
@@ -83,14 +81,13 @@
 
 import Data.Generics.Uniplate.DataOnly
 import Data.List.Extra
-import qualified Data.Map as Map
+import Data.Map qualified as Map
 
-import BasicTypes
+import GHC.Utils.Outputable
+import GHC.Types.Basic
 import GHC.Hs
-import RdrName
-import Outputable
-import Bag
-import SrcLoc
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Reader
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 smellModuleHint :: [Setting] -> ModuHint
@@ -99,7 +96,7 @@
       imports = hsmodImports mod in
   case Map.lookup SmellManyImports (smells settings) of
     Just n | length imports >= n ->
-             let span = foldl1 combineSrcSpans $ getLoc <$> imports
+             let span = foldl1 combineSrcSpans $ locA . getLoc <$> imports
                  displayImports = unlines $ f <$> imports
              in [rawIdea Config.Type.Warning "Many imports" span displayImports  Nothing [] [] ]
       where
@@ -132,7 +129,7 @@
 declSpans
    (L _ (ValD _
      FunBind {fun_matches=MG {
-                   mg_origin=FromSource
+                   mg_ext=FromSource
                  , mg_alts=(L _ [L _ Match {
                        m_ctxt=ctx
                      , m_grhss=GRHSs{grhssGRHSs=[locGrhs]
@@ -141,43 +138,42 @@
  -- the where clause.
  rhsSpans ctx locGrhs ++ whereSpans where_
 -- Any other kind of function.
-declSpans f@(L l (ValD _ FunBind {})) = [(l, warn "Long function" f f [])]
+declSpans f@(L l (ValD _ FunBind {})) = [(locA l, warn "Long function" (reLoc f) (reLoc f) [])]
 declSpans _ = []
 
 -- The span of a guarded right hand side.
-rhsSpans :: HsMatchContext RdrName -> LGRHS GhcPs (LHsExpr GhcPs) -> [(SrcSpan, Idea)]
+rhsSpans :: HsMatchContext (GenLocated SrcSpanAnnN RdrName) -> LGRHS GhcPs (LHsExpr GhcPs) -> [(SrcSpan, Idea)]
 rhsSpans _ (L _ (GRHS _ _ (L _ RecordCon {}))) = [] -- record constructors get a pass
 rhsSpans ctx (L _ r@(GRHS _ _ (L l _))) =
-  [(l, rawIdea Config.Type.Warning "Long function" l (showSDocUnsafe (pprGRHS ctx r)) Nothing [] [])]
-rhsSpans _ _ = []
+  [(locA l, rawIdea Config.Type.Warning "Long function" (locA l) (showSDocUnsafe (pprGRHS ctx r)) Nothing [] [])]
 
 -- The spans of a 'where' clause are the spans of its bindings.
-whereSpans :: LHsLocalBinds GhcPs -> [(SrcSpan, Idea)]
-whereSpans (L l (HsValBinds _ (ValBinds _ bs _))) =
-  concatMap (declSpans . (\(L loc bind) -> L loc (ValD noExtField bind))) (bagToList bs)
+whereSpans :: HsLocalBinds GhcPs -> [(SrcSpan, Idea)]
+whereSpans (HsValBinds _ (ValBinds _ bs _)) =
+  concatMap (declSpans . (\(L loc bind) -> L loc (ValD noExtField bind))) bs
 whereSpans _ = []
 
 spanLength :: SrcSpan -> Int
-spanLength (RealSrcSpan span) = srcSpanEndLine span - srcSpanStartLine span + 1
+spanLength (RealSrcSpan span _) = srcSpanEndLine span - srcSpanStartLine span + 1
 spanLength (UnhelpfulSpan _) = -1
 
 smellLongTypeLists :: LHsDecl GhcPs -> Int -> [Idea]
-smellLongTypeLists d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (L _ t)))))) n =
-  warn "Long type list" d d [] <$ filter longTypeList (universe t)
+smellLongTypeLists d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (L _ (HsSig _ _ (L _ t))))))) n =
+  warn "Long type list" (reLoc d) (reLoc d) [] <$ filter longTypeList (universe t)
   where
     longTypeList (HsExplicitListTy _ IsPromoted x) = length x >= n
     longTypeList _ = False
 smellLongTypeLists _ _ = []
 
 smellManyArgFunctions :: LHsDecl GhcPs -> Int -> [Idea]
-smellManyArgFunctions d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (L _ t)))))) n =
-  warn "Many arg function" d d [] <$  filter manyArgFunction (universe t)
+smellManyArgFunctions d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (L _ (HsSig _ _ (L _ t))))))) n =
+  warn "Many arg function" (reLoc d) (reLoc d) [] <$  filter manyArgFunction (universe t)
   where
     manyArgFunction t = countFunctionArgs t >= n
 smellManyArgFunctions _ _ = []
 
 countFunctionArgs :: HsType GhcPs -> Int
-countFunctionArgs (HsFunTy _ _ t) = 1 + countFunctionArgs (unLoc t)
+countFunctionArgs (HsFunTy _ _ _ t) = 1 + countFunctionArgs (unLoc t)
 countFunctionArgs (HsParTy _ t) = countFunctionArgs (unLoc t)
 countFunctionArgs _ = 0
 
diff --git a/src/Hint/Unsafe.hs b/src/Hint/Unsafe.hs
--- a/src/Hint/Unsafe.hs
+++ b/src/Hint/Unsafe.hs
@@ -18,17 +18,18 @@
 
 module Hint.Unsafe(unsafeHint) where
 
-import Hint.Type(DeclHint,ModuleEx(..),Severity(..),rawIdea,toSS)
+import Hint.Type(DeclHint,ModuleEx(..),Severity(..),rawIdea,toSSA)
 import Data.List.Extra
 import Refact.Types hiding(Match)
 import Data.Generics.Uniplate.DataOnly
 
 import GHC.Hs
-import OccName
-import RdrName
-import FastString
-import BasicTypes
-import SrcLoc
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name.Reader
+import GHC.Data.FastString
+import GHC.Types.Basic
+import GHC.Types.SourceText
+import GHC.Types.SrcLoc
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
@@ -45,31 +46,34 @@
 -- @
 -- is. We advise that such constants should have a @NOINLINE@ pragma.
 unsafeHint :: DeclHint
-unsafeHint _ (ModuleEx (L _ m) _) = \(L loc d) ->
-  [rawIdea Hint.Type.Warning "Missing NOINLINE pragma" loc
+unsafeHint _ (ModuleEx (L _ m)) = \ld@(L loc d) ->
+  [rawIdea Hint.Type.Warning "Missing NOINLINE pragma" (locA loc)
          (unsafePrettyPrint d)
          (Just $ trimStart (unsafePrettyPrint $ gen x) ++ "\n" ++ unsafePrettyPrint d)
-         [] [InsertComment (toSS (L loc d)) (unsafePrettyPrint $ gen x)]
+         [] [InsertComment (toSSA ld) (unsafePrettyPrint $ gen x)]
      -- 'x' does not declare a new function.
      | d@(ValD _
            FunBind {fun_id=L _ (Unqual x)
-                      , fun_matches=MG{mg_origin=FromSource,mg_alts=L _ [L _ Match {m_pats=[]}]}}) <- [d]
-     -- 'x' is a synonym for an appliciation involing 'unsafePerformIO'
+                      , fun_matches=MG{mg_ext=FromSource,mg_alts=L _ [L _ Match {m_pats=L _ []}]}}) <- [d]
+     -- 'x' is a synonym for an application involving 'unsafePerformIO'
      , isUnsafeDecl d
      -- 'x' is not marked 'NOINLINE'.
      , x `notElem` noinline]
   where
+    noInline :: FastString
+    noInline = fsLit $ '{' : '-' : '#' : " NOINLINE"
+
     gen :: OccName -> LHsDecl GhcPs
-    gen x = noLoc $
-      SigD noExtField (InlineSig noExtField (noLoc (mkRdrUnqual x))
-                      (InlinePragma (SourceText "{-# NOINLINE") NoInline Nothing NeverActive FunLike))
+    gen x = noLocA $
+      SigD noExtField (InlineSig noAnn (noLocA (mkRdrUnqual x))
+                      (InlinePragma (SourceText noInline) (NoInline (SourceText noInline)) Nothing NeverActive FunLike))
     noinline :: [OccName]
     noinline = [q | L _(SigD _ (InlineSig _ (L _ (Unqual q))
-                                                (InlinePragma _ NoInline Nothing NeverActive FunLike))
+                                                (InlinePragma _ (NoInline (SourceText noInline)) Nothing NeverActive FunLike))
         ) <- hsmodDecls m]
 
 isUnsafeDecl :: HsDecl GhcPs -> Bool
-isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_origin=FromSource,mg_alts=L _ alts}}) =
+isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_ext=FromSource,mg_alts=L _ alts}}) =
   any isUnsafeApp (childrenBi alts) || any isUnsafeDecl (childrenBi alts)
 isUnsafeDecl _ = False
 
diff --git a/src/Idea.hs b/src/Idea.hs
--- a/src/Idea.hs
+++ b/src/Idea.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE RecordWildCards, NoMonomorphismRestriction #-}
 
 module Idea(
@@ -13,10 +14,10 @@
 import Config.Type
 import HsColour
 import Refact.Types hiding (SrcSpan)
-import qualified Refact.Types as R
+import Refact.Types qualified as R
 import Prelude
-import SrcLoc
-import Outputable
+import GHC.Types.SrcLoc
+import GHC.Utils.Outputable
 import GHC.Util
 
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
@@ -33,10 +34,10 @@
     ,ideaNote :: [Note] -- ^ Notes about the effect of applying the replacement.
     ,ideaRefactoring :: [Refactoring R.SrcSpan] -- ^ How to perform this idea
     }
-    deriving (Eq,Ord)
+    deriving Eq
 
 -- I don't use aeson here for 2 reasons:
--- 1) Aeson doesn't esape unicode characters, and I want to (allows me to ignore encoding)
+-- 1) Aeson doesn't escape unicode characters, and I want to (allows me to ignore encoding)
 -- 2) I want to control the format so it's slightly human readable as well
 showIdeaJson :: Idea -> String
 showIdeaJson idea@Idea{ideaSpan=srcSpan@SrcSpan{..}, ..} = dict
@@ -89,8 +90,8 @@
 rawIdeaN :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note] -> Idea
 rawIdeaN a b c d e f = Idea [] [] a b c d e f []
 
-idea :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) =>
-         Severity -> String -> a -> b -> [Refactoring R.SrcSpan] -> Idea
+idea :: (GHC.Utils.Outputable.Outputable a, GHC.Utils.Outputable.Outputable b) =>
+         Severity -> String -> Located a -> Located b -> [Refactoring R.SrcSpan] -> Idea
 idea severity hint from to =
   rawIdea severity hint (getLoc from) (unsafePrettyPrint from) (Just $ unsafePrettyPrint to) []
 
@@ -98,29 +99,29 @@
 ideaRemove :: Severity -> String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea
 ideaRemove severity hint span from = rawIdea severity hint span from (Just "") []
 
-suggest :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) =>
-            String -> a -> b -> [Refactoring R.SrcSpan] -> Idea
+suggest :: (GHC.Utils.Outputable.Outputable a, GHC.Utils.Outputable.Outputable b) =>
+            String -> Located a -> Located b -> [Refactoring R.SrcSpan] -> Idea
 suggest = idea Suggestion
 
 suggestRemove :: String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea
 suggestRemove = ideaRemove Suggestion
 
-warn :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) =>
-         String -> a -> b -> [Refactoring R.SrcSpan] -> Idea
+warn :: (GHC.Utils.Outputable.Outputable a, GHC.Utils.Outputable.Outputable b) =>
+         String -> Located a -> Located b -> [Refactoring R.SrcSpan] -> Idea
 warn = idea Warning
 
-ignoreNoSuggestion :: (HasSrcSpan a, Outputable.Outputable a)
-                    => String -> a -> Idea
+ignoreNoSuggestion :: (GHC.Utils.Outputable.Outputable a)
+                    => String -> Located a -> Idea
 ignoreNoSuggestion hint x = rawIdeaN Ignore hint (getLoc x) (unsafePrettyPrint x) Nothing []
 
-ignore :: (HasSrcSpan a, Outputable.Outputable a) =>
-           String -> a -> a -> [Refactoring R.SrcSpan] -> Idea
+ignore :: (GHC.Utils.Outputable.Outputable a) =>
+           String -> Located a -> Located a -> [Refactoring R.SrcSpan] -> Idea
 ignore = idea Ignore
 
-ideaN :: (HasSrcSpan a, Outputable.Outputable a) =>
-          Severity -> String -> a -> a -> Idea
+ideaN :: (GHC.Utils.Outputable.Outputable a) =>
+          Severity -> String -> Located a -> Located a -> Idea
 ideaN severity hint from to = idea severity hint from to []
 
-suggestN :: (HasSrcSpan a, Outputable.Outputable a) =>
-             String -> a -> a -> Idea
+suggestN :: (GHC.Utils.Outputable.Outputable a) =>
+             String -> Located a -> Located a -> Idea
 suggestN = ideaN Suggestion
diff --git a/src/Language/Haskell/HLint.hs b/src/Language/Haskell/HLint.hs
--- a/src/Language/Haskell/HLint.hs
+++ b/src/Language/Haskell/HLint.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE PatternGuards, RecordWildCards #-}
 
 -- |  This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process
@@ -23,7 +24,7 @@
     -- * Hints
     Hint,
     -- * Modules
-    ModuleEx, parseModuleEx, createModuleEx, ParseError(..),
+    ModuleEx, parseModuleEx, createModuleEx, createModuleExWithFixities, ParseError(..),
     -- * Parse flags
     defaultParseFlags,
     ParseFlags(..), CppFlags(..), FixityInfo,
@@ -33,14 +34,14 @@
 import Config.Type
 import Config.Read
 import Idea
-import qualified Apply as H
+import Apply qualified as H
 import HLint
 import Fixity
-import FastString ( unpackFS )
+import GHC.Data.FastString ( unpackFS )
 import GHC.All
 import Hint.All hiding (resolveHints)
-import qualified Hint.All as H
-import SrcLoc
+import Hint.All qualified as H
+import GHC.Types.SrcLoc
 import CmdLine
 import Paths_hlint
 
@@ -49,6 +50,7 @@
 import System.FilePath
 import Data.Functor
 import Prelude
+import Hint.Restrict qualified as Restrict
 
 
 -- | Get the Cabal configured data directory of HLint.
@@ -56,7 +58,7 @@
 getHLintDataDir = getDataDir
 
 
--- | The function produces a tuple containg 'ParseFlags' (for 'parseModuleEx'),
+-- | The function produces a tuple containing 'ParseFlags' (for 'parseModuleEx'),
 --   and 'Classify' and 'Hint' for 'applyHints'.
 --   It approximates the normal HLint configuration steps, roughly:
 --
@@ -116,7 +118,9 @@
 splitSettings xs =
     ([x | Infix x <- xs]
     ,[x | SettingClassify x <- xs]
-    ,H.resolveHints $ [Right x | SettingMatchExp x <- xs] ++ map Left enumerate)
+    ,H.resolveHints ([Right x | SettingMatchExp x <- xs] ++ map Left enumerate)
+    <> mempty { hintModule = Restrict.restrictHint . (xs++)}
+    )
 
 
 -- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's.
@@ -141,10 +145,10 @@
 --
 -- > (filename, (startLine, startCol), (endLine, endCol))
 --
---   Following the GHC API, he end column is the column /after/ the end of the error.
+--   Following the GHC API, end column is the column /after/ the end of the error.
 --   Lines and columns are 1-based. Returns 'Nothing' if there is no helpful location information.
 unpackSrcSpan :: SrcSpan -> Maybe (FilePath, (Int, Int), (Int, Int))
-unpackSrcSpan (RealSrcSpan x) = Just
+unpackSrcSpan (RealSrcSpan x _) = Just
     (unpackFS $ srcSpanFile x
     ,(srcSpanStartLine x, srcSpanStartCol x)
     ,(srcSpanEndLine x, srcSpanEndCol x))
diff --git a/src/Refact.hs b/src/Refact.hs
--- a/src/Refact.hs
+++ b/src/Refact.hs
@@ -1,14 +1,16 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE LambdaCase #-}
 
 module Refact
     ( substVars
     , toRefactSrcSpan
-    , toSS
+    , toSS, toSSA, toSSAnc
     , checkRefactor, refactorPath, runRefactoring
     ) where
 
 import Control.Exception.Extra
 import Control.Monad
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe
 import Data.Version.Extra
 import GHC.LanguageExtensions.Type
@@ -17,16 +19,19 @@
 import System.Exit
 import System.IO.Extra
 import System.Process.Extra
-import qualified Refact.Types as R
+import Refact.Types qualified as R
 
-import qualified SrcLoc as GHC
+import GHC.Types.SrcLoc qualified as GHC
+import GHC.Parser.Annotation qualified as GHC
 
+import GHC.Util.SrcLoc (getAncLoc)
+
 substVars :: [String]
 substVars = [letter : number | number <- "" : map show [0..], letter <- ['a'..'z']]
 
 toRefactSrcSpan :: GHC.SrcSpan -> R.SrcSpan
 toRefactSrcSpan = \case
-    GHC.RealSrcSpan span ->
+    GHC.RealSrcSpan span _ ->
         R.SrcSpan (GHC.srcSpanStartLine span)
                   (GHC.srcSpanStartCol span)
                   (GHC.srcSpanEndLine span)
@@ -36,9 +41,15 @@
 
 -- | Don't crash in case ghc gives us a \"fake\" span,
 -- opting instead to show @-1 -1 -1 -1@ coordinates.
-toSS :: GHC.HasSrcSpan a => a -> R.SrcSpan
+toSS :: GHC.Located a -> R.SrcSpan
 toSS = toRefactSrcSpan . GHC.getLoc
 
+toSSA :: GHC.GenLocated (GHC.EpAnn a) e -> R.SrcSpan
+toSSA = toRefactSrcSpan . GHC.getLocA
+
+toSSAnc :: GHC.GenLocated GHC.NoCommentsLocation e -> R.SrcSpan
+toSSAnc = toRefactSrcSpan . getAncLoc
+
 checkRefactor :: Maybe FilePath -> IO FilePath
 checkRefactor = refactorPath >=> either errorIO pure
 
@@ -48,10 +59,12 @@
     mexc <- findExecutable excPath
     case mexc of
         Just exc -> do
-            ver <- readVersion . tail <$> readProcess exc ["--version"] ""
+            ver <- readVersion . NE.tail . NE.fromList <$> readProcess exc ["--version"] ""
             pure $ if ver >= minRefactorVersion
                        then Right exc
-                       else Left $ "Your version of refactor is too old, please upgrade to " ++ showVersion minRefactorVersion ++ " or later"
+                       else Left $ "Your version of refactor is too old, please install apply-refact "
+                                ++ showVersion minRefactorVersion
+                                ++ " or later. Apply-refact can be installed from Cabal or Stack."
         Nothing -> pure $ Left $ unlines
                        [ "Could not find 'refactor' executable"
                        , "Tried to find '" ++ excPath ++ "' on the PATH"
@@ -72,4 +85,4 @@
     waitForProcess phand
 
 minRefactorVersion :: Version
-minRefactorVersion = makeVersion [0,8,2,0]
+minRefactorVersion = makeVersion [0,9,1,0]
diff --git a/src/Report.hs b/src/Report.hs
--- a/src/Report.hs
+++ b/src/Report.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE RecordWildCards #-}
 
 module Report(writeReport) where
@@ -5,14 +6,14 @@
 import Idea
 import Data.Tuple.Extra
 import Data.List.Extra
-import qualified Data.List.NonEmpty as NE
+import Data.List.NonEmpty qualified as NE
 import Data.Maybe
 import Data.Version
 import Timing
 import Paths_hlint
 import HsColour
 import EmbedData
-import qualified GHC.Util as GHC
+import GHC.Util qualified as GHC
 
 
 writeTemplate :: FilePath -> [(String,[String])] -> FilePath -> IO ()
diff --git a/src/SARIF.hs b/src/SARIF.hs
new file mode 100644
--- /dev/null
+++ b/src/SARIF.hs
@@ -0,0 +1,178 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+{- |
+Description: Formats hlint ideas in the Statis Analysis Results Interchange Format (SARIF).
+License: BSD-3-Clause
+
+Supports the conversion of a list of HLint 'Idea's into SARIF.
+
+SARIF (Static Analysis Results Interchange Format) is an open interchange format
+for storing results from static analyses.
+-}
+module SARIF ( printIdeas
+             , showIdeas
+             , toJSONEncoding
+             -- * See also
+             --
+             -- $references
+             ) where
+
+import Data.Aeson hiding (Error)
+import Data.Aeson.Encoding
+import Data.ByteString.Lazy (ByteString)
+import Data.ByteString.Lazy qualified as B
+import Data.Text.Lazy (Text)
+import Data.Version (showVersion)
+import GHC.Util
+import Idea
+import Paths_hlint (version)
+
+-- | Print the given ideas to standard output.
+--
+-- For example:
+--
+-- >>> hlint ["src"] >>= printIdeas
+--
+-- For printing ideas in SARIF without dependent modules
+-- having to import "Data.Aeson" or "Data.ByteString.Lazy".
+printIdeas :: [Idea] -> IO ()
+printIdeas = B.putStr . showIdeas
+
+-- | Format the given ideas in SARIF.
+--
+-- For converting ideas to SARIF without dependent modules
+-- having to import "Data.Aeson".
+showIdeas :: [Idea] -> ByteString
+showIdeas = encodingToLazyByteString . toJSONEncoding
+
+-- | Converts the given ideas to a "Data.Aeson" encoding in SARIF.
+toJSONEncoding :: [Idea] -> Encoding
+toJSONEncoding = pairs . sarif
+
+-- | Converts the given object to a top-level @sarifLog@ object.
+--
+-- See section 3.13 "sarifLog object", SARIF specification.
+sarif :: [Idea] -> Series
+sarif ideas =
+  pair "version" (lazyText "2.1.0") <>
+  pair "$schema" (lazyText schemaURI) <>
+  pair "runs" runs
+  where runs = list pairs [ pair "tool" (pairs tool) <>
+                            pair "results" (list (pairs . toResult) ideas) ]
+
+-- | A @tool@ object describing what created the output.
+--
+-- Obviously, it will describe that HLint created the output.
+--
+-- See section 3.18 "tool object", SARIF specification.
+tool :: Series
+tool = pair "driver" $ pairs $
+  pair "name" (lazyText "hlint") <>
+  pair "version" (string $ showVersion version) <>
+  pair "informationUri" (lazyText hlintURI)
+
+-- | Converts a given idea into a @result@ object.
+--
+-- It will describe the hint, the severity, suggestions for fixes, etc.
+--
+-- See section 3.27 "result object", SARIF specification.
+toResult :: Idea -> Series
+toResult idea@Idea{..} =
+  pair "message" (pairs $ pair "text" $ string $ show idea) <>
+  pair "level" (lazyText $ showSeverity ideaSeverity) <>
+  pair "locations" (list (pairs . toLocation) [idea]) <>
+  pair "fixes" (list (pairs . toFix) [idea]) <>
+  -- Use 'ideaHint' as the rule identifier.
+  --
+  -- "ruleId" is supposed to a stable, opaque identifier.
+  -- 'ideaHint' is not opaque, nor is it quite guaranteed to be stable,
+  -- but they will usually be stable enough, and disabling a hint is
+  -- based on the name in 'ideaHint'.
+  --
+  -- Most importantly, there is no requirement that "ruleId"
+  -- be a /unique/ identifier.
+  pair "ruleId" (string ideaHint)
+
+-- | Convert HLint severity to SARIF level.
+--
+-- See section 3.58.6 "level property", SARIF specification.
+showSeverity :: Severity -> Text
+showSeverity Error = "error"
+showSeverity Warning = "warning"
+showSeverity Suggestion = "note"
+showSeverity Ignore = "none"
+
+-- | Converts the location information in a given idea to a @location@ object.
+--
+-- See section 3.28 "location object", SARIF specification.
+toLocation :: Idea -> Series
+toLocation idea@Idea{ideaSpan=SrcSpan{..}, ..} =
+  physicalLocation <> logicalLocations ideaModule ideaDecl
+  where physicalLocation = pair "physicalLocation" $ pairs $
+          pair "artifactLocation"
+              (pairs $ pair "uri" (string srcSpanFilename)) <>
+          pair "region" (pairs $ toRegion idea)
+
+        logicalLocations [mod] [decl] = pair "logicalLocations" $
+          list pairs [ pair "name" (string decl) <>
+                       pair "fullyQualifiedName" (string $ mod ++ "." ++ decl) ]
+          -- It would be nice to include whether it is a function or type
+          -- in the "kind" field, but we do not have that information.
+
+        -- If the lists are empty, then there is obviously no logical location.
+        -- Logical location is still omitted when the lists are not singleton,
+        -- because the associations between modules and declarations are
+        -- not clear.
+        logicalLocations _ _ = mempty
+
+-- | Converts a given idea to a @fix@ object.
+--
+-- It will suggest how code can be improved to deal with an issue.
+-- This includes the file to be changed and how to change it.
+--
+-- See section 3.55 "fix object", SARIF specification.
+toFix :: Idea -> Series
+toFix idea@Idea{..} =
+  pair "description" (pairs $ pair "text" $ string ideaHint) <>
+  pair "artifactChanges" (list (pairs . toChange) [idea])
+
+-- | Converts a given idea to a @artifactChange@ object.
+--
+-- It will describe the details as to how the code can be changed.
+-- I.e., the text to remove and what it should be replaced with.
+--
+-- See section 3.56 "artifactChange object", SARIF specification.
+toChange :: Idea -> Series
+toChange idea@Idea{ideaSpan=SrcSpan{..}, ..} =
+  pair "artifactLocation" (pairs uri) <>
+  pair "replacements" (list pairs [deleted <> inserted])
+  where uri  = pair "uri" $ string srcSpanFilename
+        deleted = pair "deletedRegion" $ pairs $ toRegion idea
+        inserted = maybe mempty insertedContent ideaTo
+        insertedContent = pair "insertedContent" . pairs . pair "text" . string
+
+-- | Converts the source span in an idea to a SARIF region.
+--
+-- See 3.30 "region object", SARIF specification.
+toRegion :: Idea -> Series
+toRegion Idea{ideaSpan=SrcSpan{..}, ..} =
+  pair "startLine" (int srcSpanStartLine') <>
+  pair "startColumn" (int srcSpanStartColumn) <>
+  pair "endLine" (int srcSpanEndLine') <>
+  pair "endColumn" (int srcSpanEndColumn)
+
+-- | URI to SARIF schema definition.
+schemaURI :: Text
+schemaURI = "https://raw.githubusercontent.com/" <>
+            "oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
+
+-- | URI to HLint home page.
+hlintURI :: Text
+hlintURI = "https://github.com/ndmitchell/hlint"
+
+-- $references
+--
+-- * [SARIF Tutorials](https://github.com/microsoft/sarif-tutorials)
+-- * [Static Analysis Results Interchange Format](https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/sarif-v2.1.0-cs01.html), version 2.1.0
diff --git a/src/Summary.hs b/src/Summary.hs
--- a/src/Summary.hs
+++ b/src/Summary.hs
@@ -1,10 +1,15 @@
-{-# LANGUAGE RecordWildCards, TupleSections #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DataKinds #-}
 
-module Summary (generateSummary) where
+module Summary (generateMdSummary, generateJsonSummary, generateExhaustiveConfig) where
 
-import qualified Data.Map as Map
+import Data.Map qualified as Map
 import Control.Monad.Extra
 import System.FilePath
+import Data.List.NonEmpty qualified as NE
 import Data.List.Extra
 import System.Directory
 
@@ -14,73 +19,127 @@
 import Hint.All
 import Config.Type
 import Test.Annotations
-
-
-data BuiltinKey = BuiltinKey
-  { builtinName :: !String
-  , builtinSeverity :: !Severity
-  , builtinRefactoring :: !Bool
-  } deriving (Eq, Ord)
+import Deriving.Aeson
+import Data.Aeson (encode)
+import Data.ByteString.Char8 (unpack)
+import Data.ByteString.Lazy (toStrict)
 
-data BuiltinValue = BuiltinValue
-    { builtinInp :: !String
-    , builtinFrom :: !String
-    , builtinTo :: !(Maybe String)
-    }
+data Summary = Summary
+  { sBuiltinRules :: ![BuiltinHint]
+  , sLhsRhsRules :: ![HintRule]
+  } deriving (Show, Generic)
+  deriving (ToJSON) via CustomJSON '[FieldLabelModifier (StripPrefix "s", CamelToSnake)] Summary
 
+data BuiltinHint = BuiltinHint
+  { hName :: !String
+  , hSeverity :: !Severity
+  , hRefactoring :: !Bool
+  , hCategory :: !String
+  , hExamples :: ![BuiltinExample]
+  } deriving (Show, Eq, Ord, Generic)
+  deriving (ToJSON) via CustomJSON '[FieldLabelModifier (StripPrefix "h", CamelToSnake)] BuiltinHint
 
-dedupeBuiltin :: [(BuiltinKey, BuiltinValue)] -> [(BuiltinKey, BuiltinValue)]
-dedupeBuiltin = Map.toAscList . Map.fromListWith (\_ old -> old)
+data BuiltinKey = BuiltinKey
+  { kName :: !String
+  , kSeverity :: !Severity
+  , kRefactoring :: !Bool
+  , kCategory :: !String
+  } deriving (Show, Eq, Ord)
 
+data BuiltinExample = BuiltinExample
+    { eContext :: !String
+    , eFrom :: !String
+    , eTo :: !(Maybe String)
+    } deriving (Show, Eq, Ord, Generic)
+    deriving (ToJSON) via CustomJSON '[FieldLabelModifier (StripPrefix "e", CamelToSnake)] BuiltinExample
 
--- | Generate a summary of hints, including built-in hints and YAML-configured hints
--- from @data/hlint.yaml@.
-generateSummary :: [Setting] -> IO String
-generateSummary settings = do
-    -- Do not insert if the key already exists in the map. This has the effect
-    -- of picking the first test case of a hint as the example in the summary.
-    builtinHints <- mkBuiltinSummary
-    let lhsRhsHints = [hint | SettingMatchExp hint <- settings]
-    pure $ genBuiltinSummaryMd builtinHints lhsRhsHints
+dedupBuiltin :: [(BuiltinKey, BuiltinExample)] -> [BuiltinHint]
+dedupBuiltin = fmap makeHint . Map.toAscList . Map.fromListWith (<>) . fmap exampleToList where
+  exampleToList (k, e) = (k, [e])
+  makeHint (BuiltinKey{..}, examples) = BuiltinHint
+    kName
+    kSeverity
+    kRefactoring
+    kCategory
+    examples
 
 -- | The summary of built-in hints is generated by running the test cases in
--- @src/Hint/*.hs@. One entry per (hint name, severity, does it support refactoring).
-mkBuiltinSummary :: IO [(String, [(BuiltinKey, BuiltinValue)])]
-mkBuiltinSummary = forM builtinHints $ \(name, hint) -> (name,) <$> do
-    let file = "src/Hint" </> name <.> "hs"
+-- @src/Hint/*.hs@.
+mkBuiltinSummary :: IO [BuiltinHint]
+mkBuiltinSummary = concatForM builtinHints $ \(category, hint) -> do
+    let file = "src/Hint" </> category <.> "hs"
     b <- doesFileExist file
     if not b then do
         putStrLn $ "Couldn't find source hint file " ++ file ++ ", some hints will be missing"
         pure []
      else do
         tests <- parseTestFile file
-        fmap dedupeBuiltin <$> concatForM tests $ \(TestCase _ _ inp _ _) -> do
+        fmap dedupBuiltin <$> concatForM tests $ \(TestCase _ _ inp _ _) -> do
             m <- parseModuleEx defaultParseFlags file (Just inp)
             pure $ case m of
-                Right m -> map (ideaToValue inp) $ applyHints [] hint [m]
+                Right m -> map (ideaToValue category inp) $ applyHints [] hint [m]
                 Left _ -> []
     where
-        ideaToValue :: String -> Idea -> (BuiltinKey, BuiltinValue)
-        ideaToValue inp Idea{..} = (k, v)
+        ideaToValue :: String -> String -> Idea -> (BuiltinKey, BuiltinExample)
+        ideaToValue category inp Idea{..} = (k, v)
             where
                 -- make sure Windows/Linux don't differ on path separators
                 to = fmap (\x -> if "Combine with " `isPrefixOf` x then replace "\\" "/" x else x) ideaTo
-                k = BuiltinKey ideaHint ideaSeverity (notNull ideaRefactoring)
-                v = BuiltinValue inp ideaFrom to
+                k = BuiltinKey ideaHint ideaSeverity (notNull ideaRefactoring) category
+                v = BuiltinExample inp ideaFrom to
 
+getSummary :: [Setting] -> IO Summary
+getSummary settings = do
+  builtinHints <- mkBuiltinSummary
+  let lhsRhsHints = [hint | SettingMatchExp hint <- settings]
+  pure $ Summary builtinHints lhsRhsHints
 
-genBuiltinSummaryMd :: [(String, [(BuiltinKey, BuiltinValue)])] -> [HintRule] -> String
-genBuiltinSummaryMd builtins lhsRhs = unlines $
+jsonToString :: ToJSON a => a -> String
+jsonToString = unpack . toStrict . encode
+
+-- | Generate a summary of hints, including built-in hints and YAML-configured hints
+generateMdSummary :: [Setting] -> IO String
+generateMdSummary = fmap genSummaryMd . getSummary
+
+generateJsonSummary :: [Setting] -> IO String
+generateJsonSummary = fmap jsonToString . getSummary
+
+generateExhaustiveConfig :: Severity -> [Setting] -> IO String
+generateExhaustiveConfig severity = fmap (genExhaustiveConfig severity) . getSummary
+
+genExhaustiveConfig :: Severity -> Summary -> String
+genExhaustiveConfig severity Summary{..} = unlines $
+  [ "# HLint configuration file"
+  , "# https://github.com/ndmitchell/hlint"
+  , "##########################"
+  , ""
+  , "# This file contains a template configuration file, which is typically"
+  , "# placed as .hlint.yaml in the root of your project"
+  , ""
+  , "# All built-in hints"
+  ]
+    ++ (mkLine <$> sortDedup (hName <$> sBuiltinRules))
+    ++ ["", "# All LHS/RHS hints"]
+    ++ (mkLine <$> sortDedup (hintRuleName <$> sLhsRhsRules))
+  where
+    sortDedup = fmap (NE.head . NE.fromList) . group . sort
+    mkLine name = "- " <> show severity <> ": {name: " <> jsonToString name <> "}"
+
+genSummaryMd :: Summary -> String
+genSummaryMd Summary{..} = unlines $
   [ "# Summary of Hints"
   , ""
   , "This page is auto-generated from `hlint --generate-summary`."
   ] ++
-  concat ["" : ("## Builtin " ++ group ) : "" : builtinTable hints |  (group, hints) <- builtins] ++
+  concat ["" : ("## Builtin " ++ group ) : "" : builtinTable hints | (group, hints) <- groupHintsByCategory sBuiltinRules] ++
   [ ""
   , "## Configured hints"
   , ""
   ]
-  ++ lhsRhsTable lhsRhs
+  ++ lhsRhsTable sLhsRhsRules
+  where
+    groupHintsByCategory = Map.toAscList . Map.fromListWith (<>) . fmap keyCategory
+    keyCategory hint = (hCategory hint, [hint])
 
 row :: [String] -> [String]
 row xs = ["<tr>"] ++ xs ++ ["</tr>"]
@@ -91,34 +150,35 @@
   | '\n' `elem` s = ["<pre>", s, "</pre>"]
   | otherwise = ["<code>", s, "</code>", "<br>"]
 
-builtinTable :: [(BuiltinKey, BuiltinValue)] -> [String]
+builtinTable :: [BuiltinHint] -> [String]
 builtinTable builtins =
   ["<table>"]
   ++ row ["<th>Hint Name</th>", "<th>Hint</th>", "<th>Severity</th>"]
-  ++ concatMap (uncurry showBuiltin) builtins
+  ++ concatMap showBuiltin builtins
   ++ ["</table>"]
 
-showBuiltin :: BuiltinKey -> BuiltinValue -> [String]
-showBuiltin BuiltinKey{..} BuiltinValue{..} = row1
+showBuiltin :: BuiltinHint -> [String]
+showBuiltin BuiltinHint{..} = row1
   where
     row1 = row $
-      [ "<td>" ++ builtinName ++ "</td>"
-      , "<td>"
-      , "Example:"
-      ]
-      ++ haskell builtinInp
-      ++ ["Found:"]
-      ++ haskell builtinFrom
-      ++ ["Suggestion:"]
-      ++ haskell to
-      ++ ["Does not support refactoring." | not builtinRefactoring]
+      [ "<td>" ++ hName ++ "</td>", "<td>"]
+      ++ showExample (NE.head (NE.fromList hExamples))
+      ++ ["Does not support refactoring." | not hRefactoring]
       ++ ["</td>"] ++
-      [ "<td>" ++ show builtinSeverity ++ "</td>"
+      [ "<td>" ++ show hSeverity ++ "</td>"
       ]
-    to = case builtinTo of
-      Nothing -> ""
-      Just "" -> "Perhaps you should remove it."
-      Just s -> s
+    showExample BuiltinExample{..} =
+      ["Example: "]
+        ++ haskell eContext
+        ++ ["Found:"]
+        ++ haskell eFrom
+        ++ ["Suggestion:"]
+        ++ haskell eTo'
+      where
+      eTo' = case eTo of
+        Nothing -> ""
+        Just "" -> "Perhaps you should remove it."
+        Just s -> s
 
 lhsRhsTable :: [HintRule] -> [String]
 lhsRhsTable hints =
diff --git a/src/Test/Annotations.hs b/src/Test/Annotations.hs
--- a/src/Test/Annotations.hs
+++ b/src/Test/Annotations.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE CPP, PatternGuards, RecordWildCards, ViewPatterns #-}
 
 -- | Check the <TEST> annotations within source and hint files.
@@ -17,7 +18,7 @@
 import System.FilePath
 import System.IO.Extra
 import GHC.All
-import qualified Data.ByteString.Char8 as BS
+import Data.ByteString.Char8 qualified as BS
 
 import Config.Type
 import Idea
@@ -27,10 +28,10 @@
 import Test.Util
 import Prelude
 import Config.Yaml
-import FastString
+import GHC.Data.FastString
 
 import GHC.Util
-import SrcLoc
+import GHC.Types.SrcLoc
 import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 #ifdef HS_YAML
@@ -123,7 +124,10 @@
         open line
           |  "<TEST>" `isPrefixOf` line =
              let suffix = dropPrefix "<TEST>" line
-                 config = decodeEither'  $ BS.pack suffix
+                 config =
+                   if isBuiltinYaml file
+                     then mapRight getConfigYamlBuiltin $ decodeEither' $ BS.pack suffix
+                     else mapRight getConfigYamlUser $ decodeEither' $ BS.pack suffix
              in case config of
                   Left err -> Just []
                   Right config -> Just $ settingsFromConfigYaml [config]
@@ -160,7 +164,7 @@
 testRefactor Nothing _ _ = pure []
 -- Skip refactoring test if there is no hint.
 testRefactor _ Nothing _ = pure []
--- Skip refactoring test if the hint has no suggestion (such as "Parse error" or "Avoid restricted fuction").
+-- Skip refactoring test if the hint has no suggestion (such as "Parse error" or "Avoid restricted function").
 testRefactor _ (Just idea) _ | isNothing (ideaTo idea) = pure []
 -- Skip refactoring test if the hint does not support refactoring.
 testRefactor _ (Just idea) _ | null (ideaRefactoring idea) = pure []
diff --git a/src/Test/InputOutput.hs b/src/Test/InputOutput.hs
--- a/src/Test/InputOutput.hs
+++ b/src/Test/InputOutput.hs
@@ -17,6 +17,8 @@
 import System.Exit
 import System.IO.Extra
 import Prelude
+import Data.Version (showVersion)
+import Paths_hlint (version)
 
 import Test.Util
 
@@ -48,6 +50,8 @@
     where
         z = InputOutput "unknown" [] [] "" Nothing
         interest x = any (`isPrefixOf` x) ["----","FILE","RUN","OUTPUT","EXIT"]
+        outputTemplateVars = [ ("__VERSION__", showVersion version) ]
+        substituteTemplateVars = mconcatMap (uncurry replace) outputTemplateVars
 
         f io ((stripPrefix "RUN " -> Just flags):xs) = f io{run = splitArgs flags} xs
         f io ((stripPrefix "EXIT " -> Just code):xs) = f io{exit = Just $ let i = read code in if i == 0 then ExitSuccess else ExitFailure i} xs
@@ -57,7 +61,7 @@
         f io [] = [io | io /= z]
         f io (x:xs) = error $ "Unknown test item, " ++ x
 
-        g = first (reverse . dropWhile null . reverse) . break interest
+        g = first (fmap substituteTemplateVars . reverse . dropWhile null . reverse) . break interest
 
 
 ---------------------------------------------------------------------
diff --git a/src/Timing.hs b/src/Timing.hs
--- a/src/Timing.hs
+++ b/src/Timing.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 
 module Timing(
     timed, timedIO,
@@ -5,7 +6,7 @@
     printTimings
     ) where
 
-import qualified Data.HashMap.Strict as Map
+import Data.HashMap.Strict qualified as Map
 import Control.Exception
 import Data.IORef.Extra
 import Data.Tuple.Extra
diff --git a/src/Util.hs b/src/Util.hs
--- a/src/Util.hs
+++ b/src/Util.hs
@@ -4,7 +4,7 @@
     forceList,
     gzip, universeParentBi,
     exitMessage, exitMessageImpure,
-    getContentsUTF8
+    getContentsUTF8, wildcardMatch
     ) where
 
 import System.Exit
@@ -13,6 +13,8 @@
 import Unsafe.Coerce
 import Data.Data
 import Data.Generics.Uniplate.DataOnly
+import System.FilePattern
+import Data.List.Extra
 
 
 ---------------------------------------------------------------------
@@ -64,3 +66,23 @@
 
 universeParentBi :: (Data a, Data b) => a -> [(Maybe b, b)]
 universeParentBi = concatMap universeParent . childrenBi
+
+
+---------------------------------------------------------------------
+-- SYSTEM.FILEPATTERN
+
+-- | Returns true if the pattern matches the string. For example:
+--
+-- >>> let isSpec = wildcardMatch "**.*Spec"
+-- >>> isSpec "Example"
+-- False
+-- >>> isSpec "ExampleSpec"
+-- True
+-- >>> isSpec "Namespaced.ExampleSpec"
+-- True
+-- >>> isSpec "Deeply.Nested.ExampleSpec"
+-- True
+--
+-- See this issue for details: <https://github.com/ndmitchell/hlint/issues/402>.
+wildcardMatch :: FilePattern -> String -> Bool
+wildcardMatch p m = let f = replace "." "/" in f p ?== f m
diff --git a/tests/cpp.test b/tests/cpp.test
--- a/tests/cpp.test
+++ b/tests/cpp.test
@@ -44,8 +44,8 @@
 main = undefined
 EXIT 1
 OUTPUT
-tests/cpp-ext-enable.hs:1:1: Error: Parse error: Unsupported extension: Foo
-
+tests/cpp-ext-enable.hs:1:1: Error: Parse error: tests/cpp-ext-enable.hs:3:14: error: [GHC-46537]
+    Unsupported extension: Foo
 Found:
   {-# LANGUAGE CPP #-}
 
@@ -53,7 +53,7 @@
 
   main = undefined
 
-1 hint
+1 error
 
 ---------------------------------------------------------------------
 RUN tests/cpp-ext-disable.hs
@@ -129,3 +129,39 @@
 #endif
 OUTPUT
 No hints
+
+---------------------------------------------------------------------
+RUN tests/cpp-file5.hs
+FILE tests/cpp-file5.hs
+{-# LANGUAGE CPP #-}
+main =
+#if __GLASGOW_HASKELL__ > 800
+  print __GLASGOW_HASKELL__
+#else
+  putStrLn $ show __GLASGOW_HASKELL__
+#endif
+OUTPUT
+tests/cpp-file5.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN --cpp-define "__GLASGOW_HASKELL__=123" tests/cpp-file6.hs
+FILE tests/cpp-file6.hs
+{-# LANGUAGE CPP #-}
+main =
+#if __GLASGOW_HASKELL__ == 123
+  print __GLASGOW_HASKELL__
+#else
+  putStrLn $ show __GLASGOW_HASKELL__
+#endif
+OUTPUT
+tests/cpp-file6.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
diff --git a/tests/do-block.test b/tests/do-block.test
new file mode 100644
--- /dev/null
+++ b/tests/do-block.test
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------
+RUN tests/do-block.hs --hint=data/do-block.yaml
+FILE tests/do-block.hs
+h x = case f x of
+  Just n -> g n
+  Nothing -> Nothing
+OUTPUT
+tests/do-block.hs:(1,7)-(3,20): Warning: Redundant Nothing
+Found:
+  case f x of
+    Just n -> g n
+    Nothing -> Nothing
+Perhaps:
+  do n <- f x
+     g n
+
+1 hint
diff --git a/tests/flag-ignore-suggestions.test b/tests/flag-ignore-suggestions.test
new file mode 100644
--- /dev/null
+++ b/tests/flag-ignore-suggestions.test
@@ -0,0 +1,38 @@
+---------------------------------------------------------------------
+RUN tests/flag-ignore-suggestions-1.hs --ignore-suggestions
+FILE tests/flag-ignore-suggestions-1.hs
+main = map f $ map g xs
+OUTPUT
+No hints
+
+EXIT 0
+---------------------------------------------------------------------
+RUN tests/flag-ignore-suggestions-2.hs --ignore-suggestions
+FILE tests/flag-ignore-suggestions-2.hs
+foo = (+1)
+bar x = foo x
+main = map f $ map g xs
+OUTPUT
+tests/flag-ignore-suggestions-2.hs:2:1-13: Warning: Eta reduce
+Found:
+  bar x = foo x
+Perhaps:
+  bar = foo
+
+1 hint
+
+EXIT 1
+---------------------------------------------------------------------
+RUN tests/flag-ignore-suggestions-3.hs --ignore-suggestions --show
+FILE tests/flag-ignore-suggestions-3.hs
+main = map f $ map g xs
+OUTPUT
+tests/flag-ignore-suggestions-3.hs:1:8-23: Suggestion: Use map once
+Found:
+  map f $ map g xs
+Perhaps:
+  map (f . g) xs
+
+1 hint
+
+EXIT 1
diff --git a/tests/flag-no-summary.test b/tests/flag-no-summary.test
--- a/tests/flag-no-summary.test
+++ b/tests/flag-no-summary.test
@@ -13,6 +13,6 @@
 ---------------------------------------------------------------------
 RUN tests/flag-no-summary2.hs --no-summary
 FILE tests/flag-no-summary2.hs
-main = return ()
+main = pure ()
 OUTPUT
 EXIT 0
diff --git a/tests/hint.test b/tests/hint.test
--- a/tests/hint.test
+++ b/tests/hint.test
@@ -100,6 +100,82 @@
 1 hint
 
 ---------------------------------------------------------------------
+RUN tests/restricted-badidents-bad.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-badidents-bad.lhs
+> import Restricted.Module.BadIdents (bad)
+OUTPUT
+tests/restricted-badidents-bad.lhs:1:3-42: Warning: Avoid restricted identifiers
+Found:
+  import Restricted.Module.BadIdents ( bad )
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-badidents-multibad.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-badidents-multibad.lhs
+> import Restricted.Module.BadIdents (bad, good)
+OUTPUT
+tests/restricted-badidents-multibad.lhs:1:3-48: Warning: Avoid restricted identifiers
+Found:
+  import Restricted.Module.BadIdents ( bad, good )
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-badidents-valid.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-badidents-valid.lhs
+> import Restricted.Module.BadIdents (good)
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/restricted-badidents-universal.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-badidents-universal.lhs
+> import Restricted.Module.BadIdents
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/restricted-onlyidents-bad.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-onlyidents-bad.lhs
+> import Restricted.Module.OnlyIdents (bad)
+OUTPUT
+tests/restricted-onlyidents-bad.lhs:1:3-43: Warning: Avoid restricted identifiers
+Found:
+  import Restricted.Module.OnlyIdents ( bad )
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-onlyidents-multibad.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-onlyidents-multibad.lhs
+> import Restricted.Module.OnlyIdents (bad, good)
+OUTPUT
+tests/restricted-onlyidents-multibad.lhs:1:3-49: Warning: Avoid restricted identifiers
+Found:
+  import Restricted.Module.OnlyIdents ( bad, good )
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-onlyidents-valid.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-onlyidents-valid.lhs
+> import Restricted.Module.OnlyIdents (good)
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/restricted-onlyidents-universal.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-onlyidents-universal.lhs
+> import Restricted.Module.OnlyIdents
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
 RUN tests/restricted-function.lhs --hint=data/test-restrict.yaml
 FILE tests/restricted-function.lhs
 > main = restricted ()
diff --git a/tests/hintrule-implies-classify.test b/tests/hintrule-implies-classify.test
new file mode 100644
--- /dev/null
+++ b/tests/hintrule-implies-classify.test
@@ -0,0 +1,12 @@
+---------------------------------------------------------------------
+RUN tests/hintrule-implies-classify.hs --hint=data/hintrule-implies-classify.yaml
+FILE tests/hintrule-implies-classify.hs
+x = mapM print [1, 2, 3]
+OUTPUT
+tests/hintrule-implies-classify.hs:1:5-8: Warning: Use traverse
+Found:
+  mapM
+Perhaps:
+  traverse
+
+1 hint
diff --git a/tests/import_style.test b/tests/import_style.test
new file mode 100644
--- /dev/null
+++ b/tests/import_style.test
@@ -0,0 +1,129 @@
+---------------------------------------------------------------------
+RUN tests/importStyle-1.hs --hint=data/import_style.yaml
+FILE tests/importStyle-1.hs
+import HypotheticalModule1
+import HypotheticalModule2
+import HypotheticalModule3
+import qualified HypotheticalModule3.SomeModule
+import HypotheticalModule3.SomeModule qualified
+import qualified HypotheticalModule3.OtherSubModule
+OUTPUT
+tests/importStyle-1.hs:1:1-26: Warning: Avoid restricted alias
+Found:
+  import HypotheticalModule1
+Perhaps:
+  import HypotheticalModule1 as HM1
+Note: may break the code
+
+tests/importStyle-1.hs:2:1-26: Warning: HypotheticalModule2 should be imported qualified or with an explicit import list
+Found:
+  import HypotheticalModule2
+Perhaps:
+  import qualified HypotheticalModule2
+Note: may break the code
+
+tests/importStyle-1.hs:3:1-26: Warning: HypotheticalModule3 should be imported qualified
+Found:
+  import HypotheticalModule3
+Perhaps:
+  import qualified HypotheticalModule3
+Note: may break the code
+
+tests/importStyle-1.hs:4:1-47: Warning: HypotheticalModule3.SomeModule should be imported unqualified
+Found:
+  import qualified HypotheticalModule3.SomeModule
+Perhaps:
+  import HypotheticalModule3.SomeModule
+Note: may break the code
+
+tests/importStyle-1.hs:5:1-47: Warning: HypotheticalModule3.SomeModule should be imported unqualified
+Found:
+  import HypotheticalModule3.SomeModule qualified
+Perhaps:
+  import HypotheticalModule3.SomeModule
+Note: may break the code
+
+tests/importStyle-1.hs:6:1-51: Warning: HypotheticalModule3.OtherSubModule should be imported post-qualified or unqualified
+Found:
+  import qualified HypotheticalModule3.OtherSubModule
+Perhaps:
+  import HypotheticalModule3.OtherSubModule qualified
+Note: may break the code
+
+6 hints
+
+---------------------------------------------------------------------
+RUN tests/importStyle-2.hs --hint=data/import_style.yaml
+FILE tests/importStyle-2.hs
+import HypotheticalModule1 as HM1
+import qualified HypotheticalModule2
+import HypotheticalModule2 (a, b, c, d)
+import qualified HypotheticalModule3
+import HypotheticalModule3.SomeModule
+import HypotheticalModule3.OtherSubModule qualified
+import HypotheticalModule3.OtherSubModule
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/importStyle-postqual-pos.hs --hint=data/import_style.yaml -XImportQualifiedPost
+FILE tests/importStyle-postqual-pos.hs
+import HypotheticalModule1 qualified as HM1
+import HypotheticalModule2 qualified
+import HypotheticalModule2 qualified as Arbitrary
+import HypotheticalModule3 qualified
+import HypotheticalModule3 qualified as Arbitrary
+import HypotheticalModule4 qualified as HM4
+import HypotheticalModule5 qualified
+import HypotheticalModule5 qualified as HM5
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/importStyle-postqual-neg.hs --hint=data/import_style.yaml -XImportQualifiedPost
+FILE tests/importStyle-postqual-neg.hs
+import HypotheticalModule1 qualified
+import qualified HypotheticalModule4
+import qualified HypotheticalModule4 as Verbotten
+import qualified HypotheticalModule4 as HM4
+import HypotheticalModule5 as HM5
+import qualified HypotheticalModule5
+
+OUTPUT
+tests/importStyle-postqual-neg.hs:1:1-36: Warning: Avoid restricted alias
+Found:
+  import HypotheticalModule1 qualified
+Perhaps:
+  import HypotheticalModule1 qualified as HM1
+Note: may break the code
+
+tests/importStyle-postqual-neg.hs:2:1-36: Warning: Avoid restricted alias
+Found:
+  import qualified HypotheticalModule4
+Perhaps:
+  import qualified HypotheticalModule4 as HM4
+Note: may break the code
+
+tests/importStyle-postqual-neg.hs:3:1-49: Warning: Avoid restricted alias
+Found:
+  import qualified HypotheticalModule4 as Verbotten
+Perhaps:
+  import qualified HypotheticalModule4 as HM4
+Note: may break the code
+
+tests/importStyle-postqual-neg.hs:5:1-33: Warning: HypotheticalModule5 should be imported post-qualified
+Found:
+  import HypotheticalModule5 as HM5
+Perhaps:
+  import HypotheticalModule5 qualified as HM5
+Note: may break the code
+
+tests/importStyle-postqual-neg.hs:6:1-36: Warning: HypotheticalModule5 should be imported post-qualified
+Found:
+  import qualified HypotheticalModule5
+Perhaps:
+  import HypotheticalModule5 qualified
+Note: may break the code
+
+5 hints
+---------------------------------------------------------------------
diff --git a/tests/json.test b/tests/json.test
--- a/tests/json.test
+++ b/tests/json.test
@@ -18,10 +18,10 @@
 FILE tests/json-two.hs
 foo = (+1)
 bar x = foo x
-baz = getLine >>= return . map toUpper
+baz = getLine >>= pure . upper
 OUTPUT
 [{"module":["Main"],"decl":["bar"],"severity":"Warning","hint":"Eta reduce","file":"tests/json-two.hs","startLine":2,"startColumn":1,"endLine":2,"endColumn":14,"from":"bar x = foo x","to":"bar = foo","note":[],"refactorings":"[Replace {rtype = Decl, pos = SrcSpan {startLine = 2, startCol = 1, endLine = 2, endCol = 14}, subts = [(\"body\",SrcSpan {startLine = 2, startCol = 9, endLine = 2, endCol = 12})], orig = \"bar = body\"}]"}
-,{"module":["Main"],"decl":["baz"],"severity":"Suggestion","hint":"Use <&>","file":"tests/json-two.hs","startLine":3,"startColumn":7,"endLine":3,"endColumn":39,"from":"getLine >>= return . map toUpper","to":"getLine Data.Functor.<&> map toUpper","note":[],"refactorings":"[Replace {rtype = Expr, pos = SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 39}, subts = [(\"f\",SrcSpan {startLine = 3, startCol = 28, endLine = 3, endCol = 39}),(\"m\",SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 14})], orig = \"m Data.Functor.<&> f\"}]"}]
+,{"module":["Main"],"decl":["baz"],"severity":"Suggestion","hint":"Use <&>","file":"tests/json-two.hs","startLine":3,"startColumn":7,"endLine":3,"endColumn":31,"from":"getLine >>= pure . upper","to":"getLine Data.Functor.<&> upper","note":[],"refactorings":"[Replace {rtype = Expr, pos = SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 31}, subts = [(\"f\",SrcSpan {startLine = 3, startCol = 26, endLine = 3, endCol = 31}),(\"m\",SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 14})], orig = \"m Data.Functor.<&> f\"}]"}]
 
 ---------------------------------------------------------------------
 RUN tests/json-parse-error.hs --json
diff --git a/tests/lhs.test b/tests/lhs.test
--- a/tests/lhs.test
+++ b/tests/lhs.test
@@ -31,7 +31,7 @@
 
   >
 
-1 hint
+1 error
 
 ---------------------------------------------------------------------
 RUN tests/lhs-first-line.lhs
diff --git a/tests/no_explicit_cpp.test b/tests/no_explicit_cpp.test
new file mode 100644
--- /dev/null
+++ b/tests/no_explicit_cpp.test
@@ -0,0 +1,17 @@
+---------------------------------------------------------------------
+RUN tests/NoExplicitCpp.hs -XHaskell2010
+FILE tests/NoExplicitCpp.hs
+-- We expect C-preprocessing despite `CPP` not being in the
+-- enabled extensions implied by the command line. See issue
+-- https://github.com/ndmitchell/hlint/issues/1360.
+{-# LANGUAGE CPP #-}
+#if 1
+hlint = __HLINT__
+#endif
+OUTPUT
+tests/NoExplicitCpp.hs:4:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
diff --git a/tests/parse-error.test b/tests/parse-error.test
--- a/tests/parse-error.test
+++ b/tests/parse-error.test
@@ -18,16 +18,16 @@
 
   > where
 
-1 hint
+1 error
 
 ---------------------------------------------------------------------
 RUN tests/ignore-parse-error3.hs
 FILE tests/ignore-parse-error3.hs
 {-# LANGUAGE InvalidExtension #-}
 OUTPUT
-tests/ignore-parse-error3.hs:1:1: Error: Parse error: Unsupported extension: InvalidExtension
-
+tests/ignore-parse-error3.hs:1:1: Error: Parse error: tests/ignore-parse-error3.hs:1:14: error: [GHC-46537]
+    Unsupported extension: InvalidExtension
 Found:
   {-# LANGUAGE InvalidExtension #-}
 
-1 hint
+1 error
diff --git a/tests/sarif.test b/tests/sarif.test
new file mode 100644
--- /dev/null
+++ b/tests/sarif.test
@@ -0,0 +1,38 @@
+---------------------------------------------------------------------
+RUN tests/sarif-none.hs --sarif
+FILE tests/sarif-none.hs
+foo = (+1)
+OUTPUT
+{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"hlint","version":"__VERSION__","informationUri":"https://github.com/ndmitchell/hlint"}},"results":[]}]}
+
+---------------------------------------------------------------------
+RUN tests/sarif-one.hs --sarif
+FILE tests/sarif-one.hs
+foo = (+1)
+bar x = foo x
+OUTPUT
+{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"hlint","version":"__VERSION__","informationUri":"https://github.com/ndmitchell/hlint"}},"results":[{"message":{"text":"tests/sarif-one.hs:2:1-13: Warning: Eta reduce\nFound:\n  bar x = foo x\nPerhaps:\n  bar = foo\n"},"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"tests/sarif-one.hs"},"region":{"startLine":2,"startColumn":1,"endLine":2,"endColumn":14}},"logicalLocations":[{"name":"bar","fullyQualifiedName":"Main.bar"}]}],"fixes":[{"description":{"text":"Eta reduce"},"artifactChanges":[{"artifactLocation":{"uri":"tests/sarif-one.hs"},"replacements":[{"deletedRegion":{"startLine":2,"startColumn":1,"endLine":2,"endColumn":14},"insertedContent":{"text":"bar = foo"}}]}]}],"ruleId":"Eta reduce"}]}]}
+
+---------------------------------------------------------------------
+RUN tests/sarif-two.hs --sarif
+FILE tests/sarif-two.hs
+foo = (+1)
+bar x = foo x
+baz = getLine >>= pure . upper
+OUTPUT
+{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"hlint","version":"__VERSION__","informationUri":"https://github.com/ndmitchell/hlint"}},"results":[{"message":{"text":"tests/sarif-two.hs:2:1-13: Warning: Eta reduce\nFound:\n  bar x = foo x\nPerhaps:\n  bar = foo\n"},"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"tests/sarif-two.hs"},"region":{"startLine":2,"startColumn":1,"endLine":2,"endColumn":14}},"logicalLocations":[{"name":"bar","fullyQualifiedName":"Main.bar"}]}],"fixes":[{"description":{"text":"Eta reduce"},"artifactChanges":[{"artifactLocation":{"uri":"tests/sarif-two.hs"},"replacements":[{"deletedRegion":{"startLine":2,"startColumn":1,"endLine":2,"endColumn":14},"insertedContent":{"text":"bar = foo"}}]}]}],"ruleId":"Eta reduce"},{"message":{"text":"tests/sarif-two.hs:3:7-30: Suggestion: Use <&>\nFound:\n  getLine >>= pure . upper\nPerhaps:\n  getLine Data.Functor.<&> upper\n"},"level":"note","locations":[{"physicalLocation":{"artifactLocation":{"uri":"tests/sarif-two.hs"},"region":{"startLine":3,"startColumn":7,"endLine":3,"endColumn":31}},"logicalLocations":[{"name":"baz","fullyQualifiedName":"Main.baz"}]}],"fixes":[{"description":{"text":"Use <&>"},"artifactChanges":[{"artifactLocation":{"uri":"tests/sarif-two.hs"},"replacements":[{"deletedRegion":{"startLine":3,"startColumn":7,"endLine":3,"endColumn":31},"insertedContent":{"text":"getLine Data.Functor.<&> upper"}}]}]}],"ruleId":"Use <&>"}]}]}
+
+---------------------------------------------------------------------
+RUN tests/sarif-parse-error.hs --sarif
+FILE tests/sarif-parse-error.hs
+@
+OUTPUT
+{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"hlint","version":"__VERSION__","informationUri":"https://github.com/ndmitchell/hlint"}},"results":[{"message":{"text":"tests/sarif-parse-error.hs:1:1: Error: Parse error: on input `@'\nFound:\n  > @\n"},"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"uri":"tests/sarif-parse-error.hs"},"region":{"startLine":1,"startColumn":1,"endLine":1,"endColumn":2}}}],"fixes":[{"description":{"text":"Parse error: on input `@'"},"artifactChanges":[{"artifactLocation":{"uri":"tests/sarif-parse-error.hs"},"replacements":[{"deletedRegion":{"startLine":1,"startColumn":1,"endLine":1,"endColumn":2}}]}]}],"ruleId":"Parse error: on input `@'"}]}]}
+
+---------------------------------------------------------------------
+RUN tests/sarif-note.hs --sarif
+FILE tests/sarif-note.hs
+foo = any (a ==)
+bar = foldl (&&) True
+OUTPUT
+{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[{"tool":{"driver":{"name":"hlint","version":"__VERSION__","informationUri":"https://github.com/ndmitchell/hlint"}},"results":[{"message":{"text":"tests/sarif-note.hs:1:7-16: Warning: Use elem\nFound:\n  any (a ==)\nPerhaps:\n  elem a\n"},"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"tests/sarif-note.hs"},"region":{"startLine":1,"startColumn":7,"endLine":1,"endColumn":17}},"logicalLocations":[{"name":"foo","fullyQualifiedName":"Main.foo"}]}],"fixes":[{"description":{"text":"Use elem"},"artifactChanges":[{"artifactLocation":{"uri":"tests/sarif-note.hs"},"replacements":[{"deletedRegion":{"startLine":1,"startColumn":7,"endLine":1,"endColumn":17},"insertedContent":{"text":"elem a"}}]}]}],"ruleId":"Use elem"},{"message":{"text":"tests/sarif-note.hs:2:7-21: Warning: Use and\nFound:\n  foldl (&&) True\nPerhaps:\n  and\nNote: increases laziness\n"},"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"tests/sarif-note.hs"},"region":{"startLine":2,"startColumn":7,"endLine":2,"endColumn":22}},"logicalLocations":[{"name":"bar","fullyQualifiedName":"Main.bar"}]}],"fixes":[{"description":{"text":"Use and"},"artifactChanges":[{"artifactLocation":{"uri":"tests/sarif-note.hs"},"replacements":[{"deletedRegion":{"startLine":2,"startColumn":7,"endLine":2,"endColumn":22},"insertedContent":{"text":"and"}}]}]}],"ruleId":"Use and"}]}]}
diff --git a/tests/serialise.test b/tests/serialise.test
--- a/tests/serialise.test
+++ b/tests/serialise.test
@@ -18,9 +18,9 @@
 FILE tests/serialise-two.hs
 foo = (+1)
 bar x = foo x
-baz = getLine >>= return . map toUpper
+baz = getLine >>= pure . upper
 OUTPUT
-[("tests/serialise-two.hs:2:1-13: Warning: Eta reduce\nFound:\n  bar x = foo x\nPerhaps:\n  bar = foo\n",[Replace {rtype = Decl, pos = SrcSpan {startLine = 2, startCol = 1, endLine = 2, endCol = 14}, subts = [("body",SrcSpan {startLine = 2, startCol = 9, endLine = 2, endCol = 12})], orig = "bar = body"}]),("tests/serialise-two.hs:3:7-38: Suggestion: Use <&>\nFound:\n  getLine >>= return . map toUpper\nPerhaps:\n  getLine Data.Functor.<&> map toUpper\n",[Replace {rtype = Expr, pos = SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 39}, subts = [("f",SrcSpan {startLine = 3, startCol = 28, endLine = 3, endCol = 39}),("m",SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 14})], orig = "m Data.Functor.<&> f"}])]
+[("tests/serialise-two.hs:2:1-13: Warning: Eta reduce\nFound:\n  bar x = foo x\nPerhaps:\n  bar = foo\n",[Replace {rtype = Decl, pos = SrcSpan {startLine = 2, startCol = 1, endLine = 2, endCol = 14}, subts = [("body",SrcSpan {startLine = 2, startCol = 9, endLine = 2, endCol = 12})], orig = "bar = body"}]),("tests/serialise-two.hs:3:7-30: Suggestion: Use <&>\nFound:\n  getLine >>= pure . upper\nPerhaps:\n  getLine Data.Functor.<&> upper\n",[Replace {rtype = Expr, pos = SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 31}, subts = [("f",SrcSpan {startLine = 3, startCol = 26, endLine = 3, endCol = 31}),("m",SrcSpan {startLine = 3, startCol = 7, endLine = 3, endCol = 14})], orig = "m Data.Functor.<&> f"}])]
 
 
 ---------------------------------------------------------------------
@@ -37,7 +37,7 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE CPP #-}
 OUTPUT
-[("tests/serialise-four.hs:1:1-20: Warning: Use fewer LANGUAGE pragmas\nFound:\n  {-# LANGUAGE CPP #-}\n  {-# LANGUAGE CPP #-}\nPerhaps:\n  {-# LANGUAGE CPP #-}\n",[ModifyComment {pos = SrcSpan {startLine = 1, startCol = 1, endLine = 1, endCol = 21}, newComment = "{-# LANGUAGE CPP #-}"},ModifyComment {pos = SrcSpan {startLine = 2, startCol = 1, endLine = 2, endCol = 21}, newComment = ""}])]
+[("tests/serialise-four.hs:2:1-20: Warning: Use fewer LANGUAGE pragmas\nFound:\n  {-# LANGUAGE CPP #-}\n  {-# LANGUAGE CPP #-}\nPerhaps:\n  {-# LANGUAGE CPP #-}\n",[ModifyComment {pos = SrcSpan {startLine = 2, startCol = 1, endLine = 2, endCol = 21}, newComment = "{-# LANGUAGE CPP #-}"},ModifyComment {pos = SrcSpan {startLine = 1, startCol = 1, endLine = 1, endCol = 21}, newComment = ""}])]
 
 
 ---------------------------------------------------------------------
diff --git a/tests/wildcard.test b/tests/wildcard.test
deleted file mode 100644
--- a/tests/wildcard.test
+++ /dev/null
@@ -1,45 +0,0 @@
----------------------------------------------------------------------
-RUN tests/wildcard-1.hs --hint=data/wildcard.yaml
-FILE tests/wildcard-1.hs
-module SpecNoMatch where
-main = print $ (\ _ -> 'a') ()
-OUTPUT
-tests/wildcard-1.hs:2:17-26: Suggestion: Use const
-Found:
-  \ _ -> 'a'
-Perhaps:
-  const 'a'
-
-1 hint
-
----------------------------------------------------------------------
-RUN tests/wildcard-2.hs --hint=data/wildcard.yaml
-FILE tests/wildcard-2.hs
-module Spec where
-main = print $ (\ _ -> 'a') ()
-OUTPUT
-No hints
-
----------------------------------------------------------------------
-RUN tests/wildcard-3.hs --hint=data/wildcard.yaml
-FILE tests/wildcard-3.hs
-module PrefixedSpec where
-main = print $ (\ _ -> 'a') ()
-OUTPUT
-No hints
-
----------------------------------------------------------------------
-RUN tests/wildcard-4.hs --hint=data/wildcard.yaml
-FILE tests/wildcard-4.hs
-module Namespaced.Spec where
-main = print $ (\ _ -> 'a') ()
-OUTPUT
-No hints
-
----------------------------------------------------------------------
-RUN tests/wildcard-5.hs --hint=data/wildcard.yaml
-FILE tests/wildcard-5.hs
-module Deeply.Nested.Spec where
-main = print $ (\ _ -> 'a') ()
-OUTPUT
-No hints
diff --git a/tests/wildcards.test b/tests/wildcards.test
new file mode 100644
--- /dev/null
+++ b/tests/wildcards.test
@@ -0,0 +1,816 @@
+----
+RUN tests/wildcards-a.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-a.hs
+import A as Z
+OUTPUT
+tests/wildcards-a.hs:1:1-13: Warning: Avoid restricted alias
+Found:
+  import A as Z
+Perhaps:
+  import A as A
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-xa.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xa.hs
+import XA as Z
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-a.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-a.hs
+import X.A as Z
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-y-a.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-a.hs
+import X.Y.A as Z
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-b.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-b.hs
+import B as Z
+OUTPUT
+tests/wildcards-b.hs:1:1-13: Warning: Avoid restricted alias
+Found:
+  import B as Z
+Perhaps:
+  import B as B
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-xb.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xb.hs
+import XB as Z
+OUTPUT
+tests/wildcards-xb.hs:1:1-14: Warning: Avoid restricted alias
+Found:
+  import XB as Z
+Perhaps:
+  import XB as B
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-b.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-b.hs
+import X.B as Z
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-y-b.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-b.hs
+import X.Y.B as Z
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-c.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-c.hs
+import C as Z
+OUTPUT
+tests/wildcards-c.hs:1:1-13: Warning: Avoid restricted alias
+Found:
+  import C as Z
+Perhaps:
+  import C as C
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-xc.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xc.hs
+import XC as Z
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-c.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-c.hs
+import X.C as Z
+OUTPUT
+tests/wildcards-x-c.hs:1:1-15: Warning: Avoid restricted alias
+Found:
+  import X.C as Z
+Perhaps:
+  import X.C as C
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-y-c.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-c.hs
+import X.Y.C as Z
+OUTPUT
+tests/wildcards-x-y-c.hs:1:1-17: Warning: Avoid restricted alias
+Found:
+  import X.Y.C as Z
+Perhaps:
+  import X.Y.C as C
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-d.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-d.hs
+import D as Z
+OUTPUT
+tests/wildcards-d.hs:1:1-13: Warning: Avoid restricted alias
+Found:
+  import D as Z
+Perhaps:
+  import D as D
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-xd.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xd.hs
+import XD as Z
+OUTPUT
+tests/wildcards-xd.hs:1:1-14: Warning: Avoid restricted alias
+Found:
+  import XD as Z
+Perhaps:
+  import XD as D
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-d.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-d.hs
+import X.D as Z
+OUTPUT
+tests/wildcards-x-d.hs:1:1-15: Warning: Avoid restricted alias
+Found:
+  import X.D as Z
+Perhaps:
+  import X.D as D
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-y-d.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-d.hs
+import X.Y.D as Z
+OUTPUT
+tests/wildcards-x-y-d.hs:1:1-17: Warning: Avoid restricted alias
+Found:
+  import X.Y.D as Z
+Perhaps:
+  import X.Y.D as D
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-e.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-e.hs
+module E where import E
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-xe.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xe.hs
+module XE where import E
+OUTPUT
+tests/wildcards-xe.hs:1:17-24: Warning: Avoid restricted module
+Found:
+  import E
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-e.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-e.hs
+module X.E where import E
+OUTPUT
+tests/wildcards-x-e.hs:1:18-25: Warning: Avoid restricted module
+Found:
+  import E
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-y-e.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-e.hs
+module X.Y.E where import E
+OUTPUT
+tests/wildcards-x-y-e.hs:1:20-27: Warning: Avoid restricted module
+Found:
+  import E
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-f.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-f.hs
+module F where import F
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-xf.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xf.hs
+module XF where import F
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-f.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-f.hs
+module X.F where import F
+OUTPUT
+tests/wildcards-x-f.hs:1:18-25: Warning: Avoid restricted module
+Found:
+  import F
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-y-f.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-f.hs
+module X.Y.F where import F
+OUTPUT
+tests/wildcards-x-y-f.hs:1:20-27: Warning: Avoid restricted module
+Found:
+  import F
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-g.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-g.hs
+module G where import G
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-xg.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xg.hs
+module XG where import G
+OUTPUT
+tests/wildcards-xg.hs:1:17-24: Warning: Avoid restricted module
+Found:
+  import G
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcards-x-g.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-g.hs
+module X.G where import G
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-y-g.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-g.hs
+module X.Y.G where import G
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-h.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-h.hs
+module H where import H
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-xh.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-xh.hs
+module XH where import H
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-h.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-h.hs
+module X.H where import H
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-x-y-h.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-x-y-h.hs
+module X.Y.H where import H
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-u.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-u.hs
+module U where import U
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-module-xu.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-module-xu.hs
+module XU where import U
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-module-x-u.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-module-x-u.hs
+module X.U where import U
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-module-x-y-u.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-module-x-y-u.hs
+module X.Y.U where import U
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-import-xu.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-import-xu.hs
+module U where import XU
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-import-x-u.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-import-x-u.hs
+module U where import X.U
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-import-x-y-u.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-import-x-y-u.hs
+module U where import X.Y.U
+OUTPUT
+No hints
+
+----
+RUN tests/wildcards-module-w.hs --hint=data/wildcards.yaml
+FILE tests/wildcards-module-w.hs
+module W where import U
+OUTPUT
+tests/wildcards-module-w.hs:1:16-23: Warning: Avoid restricted module
+Found:
+  import U
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-i.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-i.hs
+module I where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xi.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xi.hs
+module XI where x = y (\ _ -> z)
+OUTPUT
+tests/wildcard-xi.hs:1:24-31: Suggestion: Use const
+Found:
+  \ _ -> z
+Perhaps:
+  const z
+
+1 hint
+
+----
+RUN tests/wildcard-x-i.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-i.hs
+module X.I where x = y (\ _ -> z)
+OUTPUT
+tests/wildcard-x-i.hs:1:25-32: Suggestion: Use const
+Found:
+  \ _ -> z
+Perhaps:
+  const z
+
+1 hint
+
+----
+RUN tests/wildcard-x-y-i.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-i.hs
+module X.Y.I where x = y (\ _ -> z)
+OUTPUT
+tests/wildcard-x-y-i.hs:1:27-34: Suggestion: Use const
+Found:
+  \ _ -> z
+Perhaps:
+  const z
+
+1 hint
+
+----
+RUN tests/wildcard-j.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-j.hs
+module J where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xj.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xj.hs
+module XJ where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-j.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-j.hs
+module X.J where x = y (\ _ -> z)
+OUTPUT
+tests/wildcard-x-j.hs:1:25-32: Suggestion: Use const
+Found:
+  \ _ -> z
+Perhaps:
+  const z
+
+1 hint
+
+----
+RUN tests/wildcard-x-y-j.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-j.hs
+module X.Y.J where x = y (\ _ -> z)
+OUTPUT
+tests/wildcard-x-y-j.hs:1:27-34: Suggestion: Use const
+Found:
+  \ _ -> z
+Perhaps:
+  const z
+
+1 hint
+
+----
+RUN tests/wildcard-k.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-k.hs
+module K where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xk.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xk.hs
+module XK where x = y (\ _ -> z)
+OUTPUT
+tests/wildcard-xk.hs:1:24-31: Suggestion: Use const
+Found:
+  \ _ -> z
+Perhaps:
+  const z
+
+1 hint
+
+----
+RUN tests/wildcard-x-k.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-k.hs
+module X.K where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-y-k.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-k.hs
+module X.Y.K where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-l.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-l.hs
+module L where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xl.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xl.hs
+module XL where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-l.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-l.hs
+module X.L where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-y-l.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-l.hs
+module X.Y.L where x = y (\ _ -> z)
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-m.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-m.hs
+{-# LANGUAGE CPP #-} module M where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xm.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xm.hs
+{-# LANGUAGE CPP #-} module XM where
+OUTPUT
+tests/wildcard-xm.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-m.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-m.hs
+{-# LANGUAGE CPP #-} module X.M where
+OUTPUT
+tests/wildcard-x-m.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-y-m.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-m.hs
+{-# LANGUAGE CPP #-} module X.Y.M where
+OUTPUT
+tests/wildcard-x-y-m.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-n.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-n.hs
+{-# LANGUAGE CPP #-} module N where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xn.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xn.hs
+{-# LANGUAGE CPP #-} module XN where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-n.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-n.hs
+{-# LANGUAGE CPP #-} module X.N where
+OUTPUT
+tests/wildcard-x-n.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-y-n.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-n.hs
+{-# LANGUAGE CPP #-} module X.Y.N where
+OUTPUT
+tests/wildcard-x-y-n.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-o.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-o.hs
+{-# LANGUAGE CPP #-} module O where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xo.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xo.hs
+{-# LANGUAGE CPP #-} module XO where
+OUTPUT
+tests/wildcard-xo.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-o.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-o.hs
+{-# LANGUAGE CPP #-} module X.O where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-y-o.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-o.hs
+{-# LANGUAGE CPP #-} module X.Y.O where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-p.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-p.hs
+{-# LANGUAGE CPP #-} module P where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xp.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xp.hs
+{-# LANGUAGE CPP #-} module XP where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-p.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-p.hs
+{-# LANGUAGE CPP #-} module X.P where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-y-p.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-p.hs
+{-# LANGUAGE CPP #-} module X.Y.P where
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-q.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-q.hs
+module Q where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xq.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xq.hs
+module XQ where x = read ""
+OUTPUT
+tests/wildcard-xq.hs:1:21-24: Warning: Avoid restricted function
+Found:
+  read
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-q.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-q.hs
+module X.Q where x = read ""
+OUTPUT
+tests/wildcard-x-q.hs:1:22-25: Warning: Avoid restricted function
+Found:
+  read
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-y-q.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-q.hs
+module X.Y.Q where x = read ""
+OUTPUT
+tests/wildcard-x-y-q.hs:1:24-27: Warning: Avoid restricted function
+Found:
+  read
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-r.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-r.hs
+module R where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xr.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xr.hs
+module XR where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-r.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-r.hs
+module X.R where x = read ""
+OUTPUT
+tests/wildcard-x-r.hs:1:22-25: Warning: Avoid restricted function
+Found:
+  read
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-y-r.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-r.hs
+module X.Y.R where x = read ""
+OUTPUT
+tests/wildcard-x-y-r.hs:1:24-27: Warning: Avoid restricted function
+Found:
+  read
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-s.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-s.hs
+module S where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xs.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xs.hs
+module XS where x = read ""
+OUTPUT
+tests/wildcard-xs.hs:1:21-24: Warning: Avoid restricted function
+Found:
+  read
+Note: may break the code
+
+1 hint
+
+----
+RUN tests/wildcard-x-s.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-s.hs
+module X.S where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-y-s.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-s.hs
+module X.Y.S where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-t.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-t.hs
+module T where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-xt.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-xt.hs
+module XT where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-t.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-t.hs
+module X.T where x = read ""
+OUTPUT
+No hints
+
+----
+RUN tests/wildcard-x-y-t.hs --hint=data/wildcards.yaml
+FILE tests/wildcard-x-y-t.hs
+module X.Y.T where x = read ""
+OUTPUT
+No hints
