diff --git a/.hlint.yaml b/.hlint.yaml
new file mode 100644
--- /dev/null
+++ b/.hlint.yaml
@@ -0,0 +1,78 @@
+# HLint configuration file
+# https://github.com/ndmitchell/hlint
+##########################
+
+# 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]
+  - name: [ExistentialQuantification, MultiParamTypeClasses, NamedFieldPuns]
+  - name: [FlexibleContexts, FlexibleInstances]
+  - 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
+  - {name: [-Wno-missing-fields, -fno-cse, -O0], within: CmdLine} # for cmdargs
+  - {name: [-Wno-incomplete-patterns, -Wno-overlapping-patterns]} # the pattern match checker is not very good
+
+- modules:
+  - {name: [Data.Set, Data.HashSet], as: Set}
+  - {name: [Data.Map, Data.HashMap.Strict, Data.HashMap.Lazy], as: Map}
+  - {name: Control.Arrow, within: []}
+
+- functions:
+  - {name: unsafeInterleaveIO, within: Parallel}
+  - {name: unsafePerformIO, within: [Util.exitMessageImpure, Test.Util.ref, Timing]}
+  - {name: unsafeCoerce, within: [Util.gzip, GHC.Util.Refact.Utils]}
+  - {name: Data.List.nub, within: []}
+  - {name: Data.List.nubBy, within: []}
+  - {name: Data.List.NonEmpty.nub, within: []}
+  - {name: Data.List.NonEmpty.nubBy, within: []}
+
+
+#####################################################################
+## OTHER HINTS
+
+- warn: {name: Use explicit module export list}
+
+
+#####################################################################
+## HINTS
+
+- error: {lhs: idea Warning, rhs: warn}
+- error: {lhs: idea Suggestion, rhs: suggest}
+- error: {lhs: ideaN Warning, rhs: warnN}
+- error: {lhs: ideaN Suggestion, rhs: suggestN}
+
+- error: {lhs: occNameString (occName (unLoc x)), rhs: rdrNameStr x}
+- error: {lhs: occNameString (occName x), rhs: occNameStr x}
+- error: {lhs: noLoc (HsVar noExtField (noLoc (mkRdrUnqual (mkVarOcc x)))), rhs: strToVar x}
+
+#####################################################################
+## IGNORES
+
+# doesn't fit with the other statements
+- ignore: {name: Use let, within: [HLint, Test.All]}
+# 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,328 @@
 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
+3.2.7, released 2021-01-16
+    #1202, add missing parentheses for Avoid Lambda
+    #1201, allow matching through the & operator (similar to $)
+    #1196, fix removed parens with operator sections in some cases
+3.2.6, released 2020-12-30
+    Fixes to the release generation script
+3.2.5, released 2020-12-30
+    Fixes to the release generation script
+3.2.4, released 2020-12-30
+    #1193, add warnings for redundant flip
+    #1183, allow matches where users specify unnecessary brackets
+    #1177, remove suggestions for fromMaybe False/fromMaybe True
+    #1176, suggest use of unzip
+    #1159, spot redundant brackets due to fixities, default ignored
+    #1172, suggest reusing fromLeft/fromRight
+3.2.3, released 2020-11-23
+    #1160, never consult the .hscolour file for color preferences
+    #1171, do not refactor redundant lambda with case
+    #1169, default to -A32 (doubles speed for 4 CPU)
+    #1169, make -j actually use parallelism
+    #1167, enable refactoring for "Use lambda"
+3.2.2, released 2020-11-15
+    #1166, detect more unboxed data to avoid suggesting newtype
+    #1153, fix incorrect redundant bracket with @($foo)
+    #1163, do not suggest "Use lambda" when there are guards
+    #1160, add showIdeaANSI to show Idea values with escape codes
+3.2.1, released 2020-10-15
+    #1150, remove the Duplicate hint (was slow)
+    #1149, allow within to use module wildcards, e.g. **.Foo
+    #1141, make redundant return highlight just the return
+    #1142, suggest newtype instead of data for data family instances
+    #1138, show allowed fields in YAML error message
+    #1131, fix potential variable capture in zipWith/repeat hint
+    #1129, add hints to use const and \_ x -> x where appropriate
+3.2, released 2020-09-14
+    #75, make Windows 10 use color terminals
+    Make sure the extension removed matches what you called it
+*   #1124, make test into a flag rather than a mode, use --test
+    #1073, add LHS/RHS hints to the summary
+*   Remove test --proof, --quickcheck and --typecheck, --tempdir
+*   #1123, delete grep mode
+    #1122, show the --refactor command line with --loud
+    #1120, enable refactoring for use section
+    #1114, enable refactoring for redundant as-pattern
+    #1116, enable refactoring for redundant section
+    #1115, more TVar/TMVar hints
+    #1113, suggest x >>= pure . f becomes x <&> f
+    #1111, add hint for \x y -> (x,y) to (,), and for (,,)
+    #1108, enable refactoring for redundant variable capture
+    #1105, enable refactoring for more redundant return hints
+    #1103, enable refactoring for Redundant void
+    #1083, add hints for mempty as a function
+    #1097, enable refactoring for more avoid lambda hints
+    #1094, error on `-XFoo` if `Foo` is not a known extension
+    #1090, improve --verbose to print more information
+    #1085, support eta-reduce refactoring
+    #780, ignore dist and dist-newstyle directories
+    #1076, fix -XNoCpp and CPP around LANGUAGE extensions
+    #1077, warn on unused StandaloneKindSignatures
+    #1070, warn on unused ImportQualifiedPost
+    #1067, require apply-refact 0.8.1.0 (fixes lots of bugs)
+    #1064, don't remove OverloadedLists if there is a []
+3.1.6, released 2020-06-24
+    #1062, make sure matching inserts brackets if required
+    #1058, weaken the self-definition check to match more things
+    #1060, suggest [] ++ x and [] ++ x to x
+3.1.5, released 2020-06-19
+    #1049, suggest or/and from True `elem` xs and False `notElem` xs
+    #1055, avoid incorrect hints with nested (.)'s
+    #1054, make isLitString work again
+    #1038, make -XNoTemplateHaskell imply -XNoTemplateHaskellQuotes
+    #970, require an arg to suggest fromMaybe True ==> (Just True ==)
+    #1047, suggest pushing take outside zip
+    #1041, fix language/pragma ordering in refactor
+    #1040, fix refactoring for "Avoid lambda"
+    #1042, fix redundant lambda refactoring
+    #1039, don't suggest move map inside list comp repeatedly
+    #1035, fix refactoring for "Use fewer imports" in some cases
+    #1036, disable refactoring for Use camelCase
+    #766, match quasi quotes properly in rules
+    Ignore [Char] to String hints by default
+    #1034, remove suggestions to use heirarchical module names
+    #1032, fix refactoring for "Use :"
+    #1028, add hints around sequenceA/traverse
+    #1027, pass enabled and disabled extensions to apply-refact
+    #1024, make the redundant bracket hints cover just the bracket
+    #1024, make redundant $ display more context on the command line
+    Suggest removing OverloadedLabels if there are no labels
+    #367, suggest removing OverloadedLists if there are no lists
+    #1023, speed up checking on large files (up to 12%)
+3.1.4, released 2020-05-31
+    #1018, stop --cross being quadratic
+    #1019, more rules suggesting even/odd
+3.1.3, released 2020-05-25
+    #1016, check scopes of restricted functions
+3.1.2, released 2020-05-24
+    #1014, don't error on empty do blocks
+    #1008, make redundant do ignored by default
+    #1012, add CodeWorld hints around pictures
+    #1003, enable refactoring for (v1 . v2) <$> v3
+    #1002, warn on unused NumericUnderscores
+3.1.1, released 2020-05-13
+    #993, deal with infix declarations in the module they occur
+    #993, make createModuleEx use the default HLint fixities
+    #995, add unpackSrcSpan to the API
+3.1, released 2020-05-07
+    #979, suggest removing flip only for simple final variables
+    #978, do is not redundant with non-decreasing indentation
+    #969, wrong redundant bracket suggestion with BlockArguments
+    #970, detect redundant sections, (a +) b ==> a + b
+*   #974, split ParseFlags.extensions into enabled/disabled
+    #971, add support for -XNoFoo command line flags
+    #976, run refactor even if no hints
+    #971, add support for NoFoo language pragmas
+3.0.4, released 2020-05-03
+    #968, fail on all parse errors
+    #967, enable TypeApplications by default
+3.0.3, released 2020-05-03
+    #965, fix incorrect avoid lambda suggestion
+3.0.2, released 2020-05-03
+    #963, don't generate use-section hints for tuples
+    #745, fix up free variables for A{x}, fixes list comp hints
+3.0.1, released 2020-05-02
+    #961, don't crash on non-extension LANGUAGE pragmas, e.g. Safe
+3.0, released 2020-05-02
+    Be more permissive with names'with'quotes'in
+    #953, fix incorrect suggestions with free variables and \case
+    #955, make --find generate fixity: not infix:
+    #952, improve refactorings with qualified imports
+    #945, suggest Map.fromList [] ==> Map.empty
+    #949, warn about redundant fmaps with binds
+    #950, reduce the span of "Redundant $" to only cover the "$"
+    #944, reduce the span of "Use let" to only cover the "let" line
+    #669, don't suggest replacing reverse . sort (it's quite fast)
+    #939, reduce the span of "Redundant where" to only cover the "where"
+    Remove support for GHC 8.4
+    Remove support for _eval,
+    #926, fix refactoring when the hint contains _noParen_
+    #933, improve the output for Redundant do hints
+*   Merge ParseMode into ParseFlags
+*   Rename Language.Haskell.HLint3,HLint4 to Language.Haskell.HLint
+*   Delete the old Language.Haskell.HLint
+    #881, add a monomorphic group of hints
+    #837, don't suggest redundant do if its being used for brackets
+    #923, don't suggest eta reducing infix definitions
+    #931, disable StaticPointers extension by default
+    #924, remove dependency on haskell-src-exts
+    #922, reduce the span of "Redundant do" to only cover the "do"
+    #919, more specific names for foldMap fusion rules
+    #918, warn on unused TypeOperators
+    #916, warn on unused InstanceSigs
+    Improve parse error context messages
+*   Most parse errors are fixed
+    #881, disable hints about maybe that are sometimes wrong
+    #909, be more careful about redundant bracket warnings
+    #905, match hints even if there is composition to the left
+    #904, suggest map/fromMaybe[] becomes maybe [] map
+*   Remove the hse command line argument, to parse a file with HSE
+    #901, warn on unused MultiWayIf
+    Don't raise a parse error if haskell-src-exts can't parse code
+    #899, warn on unused PatternSynonyms
+    #898, don't suggest removing NamedFieldPuns with record updates
+*   Make any --hint flag disable implicit .hlint.yaml search
+*   Delete the --with flag
+*   Haskell hint definitions are no longer supported (use YAML)
+*   Report hints with src-span information, e.g. file:1:1-10
+*   Delete resolveHints (it was the identity)
+*   Change to GHC types in the API
+    Add --with-group=future to add return ==> pure hint
+    #888, suggest foldr from (.) to ($) in some cases
+    #884, add more >=> operator hints
+    #875, fix the extension implication information
+    Add --with-group=extra to give extra library hints
+    #873, add more Applicative hints
+    #872, fix refactoring in hints to use lists
+    #871, warn when fmapping the result of gets or asks
+    #869, improve hints for maybe/fromMaybe on Bool
 2.2.11, released 2020-02-09
     #868, fix some brackets in refactoring suggestions
     #865, suggest biList if generalise-for-conciseness is turned on
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Neil Mitchell 2006-2020.
+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,22 +1,27 @@
-# 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) [![Linux build status](https://img.shields.io/travis/ndmitchell/hlint/master.svg?label=Linux%20build)](https://travis-ci.org/ndmitchell/hlint) [![Windows build status](https://img.shields.io/appveyor/ci/ndmitchell/hlint/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/ndmitchell/hlint)
+# 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:
 
-* [Installing and running HLint](#installing-and-running-hlint)
-* [FAQ](#faq)
-* [Customizing the hints](#customizing-the-hints)
-* [Hacking HLint](#hacking-hlint)
+* [Installing and running HLint](./README.md#installing-and-running-hlint)
+* [FAQ](./README.md#faq)
+* [Customizing the hints](./README.md#customizing-the-hints)
+* [Hacking HLint](./README.md#hacking-hlint)
 
 ### Bugs and limitations
 
 Bugs can be reported [on the bug tracker](https://github.com/ndmitchell/hlint/issues). There are some issues that I do not intend to fix:
 
-* 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.
+* 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
 
@@ -52,6 +57,8 @@
 
 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 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.
 
 ### Suggested usage
@@ -60,7 +67,7 @@
 
 1. Initially, run `hlint . --report` to generate `report.html` containing a list of all issues HLint has found. Fix those you think are worth fixing and keep repeating.
 1. Once you are happy, run `hlint . --default > .hlint.yaml`, which will generate a settings file ignoring all the hints currently outstanding. Over time you may wish to edit the list.
-1. For larger projects, add [custom hints or rules](#customizing-the-hints).
+1. For larger projects, add [custom hints or rules](./README.md#customizing-the-hints).
 
 Most hints are intended to be a good idea in most circumstances, but not universally - judgement is required. When contributing to someone else's project, HLint can identify pieces of code to look at, but only make changes you consider improvements - not merely to adhere to HLint rules.
 
@@ -81,37 +88,25 @@
 HLint is integrated into lots of places:
 
 * Lots of editors have HLint plugins (quite a few have more than one HLint plugin).
-* HLint is part of the multiple editor plugins [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero).
+* HLint is part of the multiple Haskell IDEs, [haskell-language-server](https://github.com/haskell/haskell-language-server), [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero).
 * [HLint Source Plugin](https://github.com/ocharles/hlint-source-plugin) makes HLint available as a GHC plugin.
+* [Splint](https://github.com/tfausak/splint) is another source plugin that doesn't require reparsing the GHC source if you are on the latest GHC version.
 * [Code Climate](https://docs.codeclimate.com/v1.0/docs/hlint) is a CI for analysis which integrates HLint.
 * [Danger](http://allocinit.io/haskell/danger-and-hlint/) can be used to automatically comment on pull requests with HLint suggestions.
 * [Restyled](https://restyled.io) includes an HLint Restyler to automatically run `hlint --refactor` on files changed in GitHub Pull Requests.
-* [lpaste](http://lpaste.net/) integrates with HLint - suggestions are shown at the bottom.
 * [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
 
-By supplying the `--refactor` flag hlint can automatically apply most
-suggestions. Instead of a list of hints, hlint will instead output the
-refactored file on stdout. In order to do this, it is necessary to have the
-`refactor` executable on you path. `refactor` is provided by the
-[`apply-refact`](https://github.com/mpickering/apply-refact) package,
-it uses the GHC API in order to transform source files given a list of
-refactorings to apply. Hlint directly calls the executable to apply the
-suggestions.
-
-Additional configuration can be passed to `refactor` with the
-`--refactor-options` flag. Some useful flags include `-i` which replaces the
-original file and `-s` which asks for confirmation before performing a hint.
-
-An alternative location for `refactor` can be specified with the
-`--with-refactor` flag.
+HLint can automatically apply some suggestions using the `--refactor` flag. If passed, instead of printing out the hints, HLint will output the refactored file on stdout. For `--refactor` to work it is necessary to have the `refactor` executable from the [`apply-refact`](https://github.com/mpickering/apply-refact) package on your `$PATH`. HLint uses that tool to perform the refactoring.
 
-Simple bindings for [vim](https://github.com/mpickering/hlint-refactor-vim),
-[emacs](https://github.com/mpickering/hlint-refactor-mode) and [atom](https://github.com/mpickering/hlint-refactor-atom) are provided.
+When using `--refactor` you can pass additional options to the `refactor` binary using `--refactor-options` flag. Some useful flags include `-i` (which replaces the original file) and `-s` (which asks for confirmation before performing a hint). The `--with-refactor` flag can be used to specify an alternative location for the `refactor` binary. Simple bindings for [Vim](https://github.com/mpickering/hlint-refactor-vim), [Emacs](https://github.com/mpickering/hlint-refactor-mode) and [Atom](https://github.com/mpickering/hlint-refactor-atom) are available.
 
-There are no plans to support the duplication nor the renaming hints.
+While the `--refactor` flag is useful, not all hints support refactoring. See [hints.md](hints.md) for which hints don't support refactoring.
 
 ### Reports
 
@@ -151,36 +146,13 @@
 
 ## FAQ
 
-### Why are hints not applied recursively?
-
-Consider:
-
-```haskell
-foo xs = concat (map op xs)
-```
-
-This will suggest eta reduction to `concat . map op`, and then after making that change and running HLint again, will suggest use of `concatMap`. Many people wonder why HLint doesn't directly suggest `concatMap op`. There are a number of reasons:
-
-* HLint aims to both improve code, and to teach the author better style. Doing modifications individually helps this process.
-* Sometimes the steps are reasonably complex, by automatically composing them the user may become confused.
-* Sometimes HLint gets transformations wrong. If suggestions are applied recursively, one error will cascade.
-* Some people only make use of some of the suggestions. In the above example using concatMap is a good idea, but sometimes eta reduction isn't. By suggesting them separately, people can pick and choose.
-* Sometimes a transformed expression will be large, and a further hint will apply to some small part of the result, which appears confusing.
-* Consider `f $ (a b)`. There are two valid hints, either remove the $ or remove the brackets, but only one can be applied.
+### Usage
 
-### Why doesn't the compiler automatically apply the optimisations?
+#### Why doesn't the compiler automatically apply the optimisations?
 
 HLint doesn't suggest optimisations, it suggests code improvements - the intention is to make the code simpler, rather than making the code perform faster. The [GHC compiler](http://haskell.org/ghc/) automatically applies many of the rules suggested by HLint, so HLint suggestions will rarely improve performance.
 
-### Why doesn't HLint know the fixity for my custom !@%$ operator?
-
-HLint knows the fixities for all the operators in the base library, but no others. HLint works on a single file at a time, and does not resolve imports, so cannot see fixity declarations from imported modules. You can tell HLint about fixities by putting them in a hint file, or passing them on the command line. For example, pass `--with=infixr 5 !@%$`, or put all the fixity declarations in a `.hlint.yaml` file as `- fixity: "infixr 5 !@%$"`. You can also use [--find](https://rawgithub.com/ndmitchell/hlint/master/hlint.htm#find) to automatically produce a list of fixity declarations in a file.
-
-### Which hints are used?
-
-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`. If you pass any `--with` hint you will need to explicitly add any `--hint` flags required.
-
-### Why do I sometimes get a "Note" with my hint?
+#### Why do I sometimes get a "Note" with my hint?
 
 Most hints are perfect substitutions, and these are displayed without any notes. However, some hints change the semantics of your program - typically in irrelevant ways - but HLint shows a warning note. HLint does not warn when assuming typeclass laws (such as `==` being symmetric). Some notes you may see include:
 
@@ -188,7 +160,7 @@
 * __Decreases laziness__ - for example `(fst a, snd a)` suggests `a` including this note. On evaluation the new code will raise an error if a is an error, while the old code would produce a pair containing two error values. Only a small number of hints decrease laziness, and anyone relying on the laziness of the original code would be advised to include a comment.
 * __Removes error__ - for example `foldr1 (&&)` suggests `and` including the note `Removes error on []`. The new code will produce `True` on the empty list, while the old code would raise an error. Unless you are relying on the exception thrown by the empty list, this hint is safe - and if you do rely on the exception, you would be advised to add a comment.
 
-### What is the difference between error/warning/suggestion?
+#### What is the difference between error/warning/suggestion?
 
 Every hint has a severity level:
 
@@ -198,8 +170,80 @@
 
 The difference between warning and suggestion is one of personal taste, typically my personal taste. If you already have a well developed sense of Haskell style, you should ignore the difference. If you are a beginner Haskell programmer you may wish to focus on warning hints before suggestion hints.
 
-### Is it possible to use pragma annotations in code that is read by `ghci` (conflicts with `OverloadedStrings`)?
+#### Why do I get a parse error?
 
+HLint enables/disables a set of extensions designed to allow as many files to parse as possible, but sometimes you'll need to enable an additional extension (e.g. Arrows, QuasiQuotes, ...), or disable some (e.g. MagicHash) to enable your code to parse.
+
+You can enable extensions by specifying additional command line arguments in [.hlint.yaml](./README.md#customizing-the-hints), e.g.: `- arguments: [-XQuasiQuotes]`.
+
+#### How do I only run hlint on changed files?
+
+If you're using git, it may be helpful to only run hlint on changed files. This can be a considerable speedup on very large codebases.
+
+```bash
+{ git diff --diff-filter=d --name-only $(git merge-base HEAD origin/master) -- "***.hs" && git ls-files -o --exclude-standard -- "***.hs"; } | xargs hlint
+```
+
+Because hlint's `--refactor` option only works when you pass a single file, this approach is also helpful to enable refactoring many files in a single command:
+
+```bash
+{ git diff --diff-filter=d --name-only $(git merge-base HEAD origin/master) -- "***.hs" && git ls-files -o --exclude-standard -- "***.hs"; } | xargs -I file hlint file --refactor --refactor-options="--inplace --step"
+```
+
+### Configuration
+
+#### Why doesn't HLint know the fixity for my custom !@%$ operator?
+
+HLint knows the fixities for all the operators in the base library, as well as operators whose fixities are declared in the module being linted, but no others. HLint works on a single file at a time, and does not resolve imports, so cannot see fixity declarations from imported modules. You can tell HLint about fixities by putting them in a hint file named `.hlint.yaml` with the syntax `- fixity: "infixr 5 !@%$"`. You can also use `--find` to automatically produce a list of fixity declarations in a file.
+
+#### Which hints are ignored?
+
+Some hints are off-by-default. Some are ignored by the configuration settings. To see all hints pass `--show`. This feature is often useful in conjunction with `--report` which shows the hints in an interactive web page, allowing them to be browsed broken down by hint.
+
+#### Which hints are used?
+
+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?
+
+Consider:
+
+```haskell
+foo xs = concat (map op xs)
+```
+
+This will suggest eta reduction to `concat . map op`, and then after making that change and running HLint again, will suggest use of `concatMap`. Many people wonder why HLint doesn't directly suggest `concatMap op`. There are a number of reasons:
+
+* HLint aims to both improve code, and to teach the author better style. Doing modifications individually helps this process.
+* Sometimes the steps are reasonably complex, by automatically composing them the user may become confused.
+* Sometimes HLint gets transformations wrong. If suggestions are applied recursively, one error will cascade.
+* Some people only make use of some of the suggestions. In the above example using concatMap is a good idea, but sometimes eta reduction isn't. By suggesting them separately, people can pick and choose.
+* Sometimes a transformed expression will be large, and a further hint will apply to some small part of the result, which appears confusing.
+* Consider `f $ (a b)`. There are two valid hints, either remove the $ or remove the brackets, but only one can be applied.
+
+#### Is it possible to use pragma annotations in code that is read by `ghci` (conflicts with `OverloadedStrings`)?
+
 Short answer: yes, it is!
 
 If the language extension `OverloadedStrings` is enabled, `ghci` may however report error messages such as:
@@ -217,10 +261,6 @@
 
 See discussion in [issue #372](https://github.com/ndmitchell/hlint/issues/372).
 
-### Why do I get a parse error?
-
-HLint enables/disables a set of extensions designed to allow as many files to parse as possible, but sometimes you'll need to enable an additional extension (e.g. Arrows), or disable some (e.g. MagicHash) to enable your code to parse. In addition, sometimes the underlying parser library ([haskell-src-exts](https://github.com/haskell-suite/haskell-src-exts)) has a bug which causes a parse error.
-
 ## Customizing the hints
 
 To customize the hints given by HLint, create a file `.hlint.yaml` in the root of your project. For a suitable default run:
@@ -229,28 +269,52 @@
 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
+
+Hints are named with the string they display in their help message
+
+For example, if hlints outputs a warning like
+
+```
+./backend/tests/api-tests/src/Main.hs:116:51: Warning: Redundant ==
+Found:
+  regIsEnabled rr == True
+Perhaps:
+  regIsEnabled rr
+```
+
+the name of the lint is `Redundant ==`.
+
+You can use that name to refer to the lint in the configuration file and `ANN` pragmas, see the following sections.
+
 ### Ignoring hints
 
 Some of the hints are subjective, and some users believe they should be ignored. Some hints are applicable usually, but occasionally don't always make sense. The ignoring mechanism provides features for suppressing certain hints. Ignore directives can either be written as pragmas in the file being analysed, or in the hint files. Examples of pragmas are:
 
 * `{-# 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`.
 
@@ -260,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.
@@ -319,25 +383,85 @@
   - {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). You can run the tests either from within a `ghci` session by typing `:test` or by running the standalone binary's tests via `cabal run hlint test` or `stack init && stack run hlint test`.
+Contributions to HLint are most welcome, following [my standard contribution guidelines](https://github.com/ndmitchell/neil/blob/master/README.md#contributions).
 
-New tests for individual hints can be added directly to source and hint files by adding annotations bracketed in `<TEST></TEST>` code comment blocks. As some examples:
+### How to run tests
 
+You can run the tests either from within a `ghci` session by typing `:test` or by running the standalone binary's tests via `cabal run -- hlint --test` or `stack run -- hlint --test`. After changing hints, you will need to regenerate the [hints.md](hints.md) file with `hlint --generate-summary`.
+
+### How to add tests
+
+New tests for individual hints can be added directly to source and hint files by adding annotations bracketed in `<TEST></TEST>` code comment blocks. Here are some examples:
+
 ```haskell
 {-
     Tests to check the zipFrom hint works
@@ -352,6 +476,16 @@
 
 The general syntax is `lhs -- rhs` with `lhs` being the expression you expect to be rewritten as `rhs`. The absence of `rhs` means you expect no hints to fire. In addition `???` lets you assert a warning without a particular suggestion, while `@` tags require a specific severity -- both these features are used less commonly.
 
+### Printing abstract syntax
+
+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
 
-This program has only been made possible by the presence of the [haskell-src-exts](https://github.com/haskell-suite/haskell-src-exts) package, and many improvements have been made by [Niklas Broberg](http://www.nbroberg.se) in response to feature requests. Additionally, many people have provided help and patches, including Lennart Augustsson, Malcolm Wallace, Henk-Jan van Tuyl, Gwern Branwen, Alex Ott, Andy Stewart, Roman Leshchinskiy, Johannes Lippmann, Iustin Pop, Steve Purcell, Mitchell Rosen and others.
+Many improvements to this program have been made by [Niklas Broberg](http://www.nbroberg.se) in response to feature requests. Additionally, many people have provided help and patches, including Lennart Augustsson, Malcolm Wallace, Henk-Jan van Tuyl, Gwern Branwen, Alex Ott, Andy Stewart, Roman Leshchinskiy, Johannes Lippmann, Iustin Pop, Steve Purcell, Mitchell Rosen and others.
diff --git a/data/HLint_QuickCheck.hs b/data/HLint_QuickCheck.hs
--- a/data/HLint_QuickCheck.hs
+++ b/data/HLint_QuickCheck.hs
@@ -102,7 +102,6 @@
         Right v -> Just v
 
 _noParen_ = id
-_eval_ = id
 
 withMain :: IO () -> IO ()
 withMain act = do
diff --git a/data/HLint_TypeCheck.hs b/data/HLint_TypeCheck.hs
--- a/data/HLint_TypeCheck.hs
+++ b/data/HLint_TypeCheck.hs
@@ -6,7 +6,6 @@
 (==>) = undefined
 
 _noParen_ = id
-_eval_ = id
 
 
 ---------------------------------------------------------------------
diff --git a/data/default.yaml b/data/default.yaml
--- a/data/default.yaml
+++ b/data/default.yaml
@@ -34,6 +34,12 @@
 # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar"
 # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x}
 
+# The hints are named by the string they display in warning messages.
+# For example, if you see a warning starting like
+#
+# Main.hs:116:51: Warning: Redundant ==
+#
+# You can refer to that hint with `{name: Redundant ==}` (see below).
 
 # Turn on hints that are off by default
 #
@@ -45,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-restrict-extensions.yaml b/data/hlint-restrict-extensions.yaml
new file mode 100644
--- /dev/null
+++ b/data/hlint-restrict-extensions.yaml
@@ -0,0 +1,3 @@
+- extensions:
+  - {name: DeriveFunctor, within: []}
+  - {name: DeriveTraversable, within: [], message: "Custom message"}
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
@@ -50,6 +55,16 @@
     - import Data.Attoparsec.Text
     - import Data.Attoparsec.ByteString
 
+- package:
+    name: quickcheck
+    modules:
+    - import Test.QuickCheck
+
+- package:
+    name: codeworld-api
+    modules:
+    - import CodeWorld
+
 - group:
     name: default
     enabled: true
@@ -60,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}
@@ -99,17 +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}
-    - 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}
@@ -118,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}
@@ -142,8 +176,11 @@
     - warn: {lhs: foldl f (head x) (tail x), rhs: foldl1 f x}
     - warn: {lhs: foldr f (last x) (init x), rhs: foldr1 f x}
     - warn: {lhs: "foldr (\\c a -> x : a) []", rhs: "map (\\c -> x)"}
+    - 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}
@@ -161,6 +198,7 @@
     - warn: {lhs: all f (map g x), rhs: all (f . g) x}
     - warn: {lhs: "zipWith (,)", rhs: zip}
     - warn: {lhs: "zipWith3 (,,)", rhs: zip3}
+    - hint: {lhs: map fst &&& map snd, rhs: unzip}
     - hint: {lhs: length x == 0, rhs: null x, note: IncreasesLaziness}
     - hint: {lhs: 0 == length x, rhs: null x, note: IncreasesLaziness}
     - hint: {lhs: length x < 1, rhs: null x, note: IncreasesLaziness}
@@ -175,12 +213,17 @@
     - hint: {lhs: 0 /= length x, rhs: not (null x), note: IncreasesLaziness, name: Use null}
     - 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}
@@ -196,6 +239,12 @@
     - warn: {lhs: all (a /=), rhs: notElem a, note: ValidInstance Eq a}
     - warn: {lhs: elem True, rhs: or}
     - 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}
@@ -205,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}
@@ -215,10 +265,16 @@
     - 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: zipWith f y (repeat z), rhs: map (\x -> f x z) y}
+    - 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}
 
     # MONOIDS
 
@@ -226,21 +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: sequence (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 (fmap g x), rhs: foldMap (f . g) x}
-    - warn: {lhs: foldMap f (map g x), rhs: foldMap (f . g) 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
 
@@ -256,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}
@@ -283,6 +357,9 @@
 
     - warn: {lhs: \x -> x, rhs: id}
     - warn: {lhs: \x y -> x, rhs: const}
+    - warn: {lhs: curry fst, rhs: const}
+    - warn: {lhs: curry snd, rhs: \_ x -> x, note: "Alternatively, use const id"}
+    - warn: {lhs: flip const, rhs: \_ x -> x, note: "Alternatively, use const id"}
     - warn: {lhs: "\\(x,y) -> y", rhs: snd}
     - warn: {lhs: "\\(x,y) -> x", rhs: fst}
     - hint: {lhs: "\\x y -> f (x,y)", rhs: curry f}
@@ -290,26 +367,37 @@
     - 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}
+    - warn: {lhs: flip f x y, rhs: f y x, side: isApp original && isAtom y}
     - warn: {lhs: id x, rhs: x}
     - warn: {lhs: id . x, rhs: x, name: Redundant id}
     - warn: {lhs: x . id, rhs: x, name: Redundant id}
     - warn: {lhs: "((,) x)", rhs: "(_noParen_ x,)", name: Use tuple-section, note: RequiresExtension TupleSections}
     - warn: {lhs: "flip (,) x", rhs: "(,_noParen_ x)", name: Use tuple-section, note: RequiresExtension TupleSections}
+    - warn: {lhs: flip (flip f), rhs: f, note: DecreasesLaziness}
+    - warn: {lhs: flip f <*> g, rhs: f =<< g, name: Redundant flip}
+    - 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}
 
@@ -319,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}
@@ -344,8 +440,6 @@
     - warn: {lhs: if x then False else y, rhs: not x && y, side: notEq y True, name: Redundant if}
     - warn: {lhs: if x then y else True, rhs: not x || y, side: notEq y False, name: Redundant if}
     - warn: {lhs: not (not x), rhs: x, name: Redundant not}
-    # warn "Too strict if": {lhs: if c then f x else f y, rhs: f (if c then x else y), note: IncreasesLaziness}
-    # also breaks types, see #87
 
     # ARROW
 
@@ -381,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}
-    - hint: {lhs: fmap f $ x, rhs: f Control.Applicative.<$> x, side: isApp x || isAtom x}
+    - 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}
@@ -396,63 +494,119 @@
     - 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 >>= return . f, rhs: f <$> m}
+    - 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}
+    - warn: {lhs: x Data.Functor.<&> f >>= g, rhs: x >>= g . f}
+    - 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: (=<<)}
     - warn: {lhs: flip (=<<), rhs: (>>=)}
     - hint: {lhs: \x -> f x >>= g, rhs: f Control.Monad.>=> g}
     - hint: {lhs: \x -> f =<< g x, rhs: f Control.Monad.<=< g}
+    - hint: {lhs: (>>= f) . g, rhs: f Control.Monad.<=< g}
+    - 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
 
@@ -461,11 +615,17 @@
 
     # 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}
     - warn: {lhs: sequence (replicate n x), rhs: Control.Monad.replicateM n x}
     - warn: {lhs: sequence_ (replicate n x), rhs: Control.Monad.replicateM_ n x}
+    - warn: {lhs: sequenceA (zipWith f x y), rhs: Control.Monad.zipWithM f x y}
+    - warn: {lhs: sequenceA_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y}
+    - warn: {lhs: sequenceA (replicate n x), rhs: Control.Monad.replicateM n x}
+    - warn: {lhs: sequenceA_ (replicate n x), rhs: Control.Monad.replicateM_ n x}
     - warn: {lhs: mapM f (replicate n x), rhs: Control.Monad.replicateM n (f x)}
     - warn: {lhs: mapM_ f (replicate n x), rhs: Control.Monad.replicateM_ n (f x)}
     - warn: {lhs: mapM f (map g x), rhs: mapM (f . g) x, name: Fuse mapM/map}
@@ -482,13 +642,17 @@
     - 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: liftA2 f (return x), rhs: fmap (f x)}
     - warn: {lhs: Just <$> a <|> pure Nothing, rhs: optional a}
-    - hint: {lhs: m >>= pure . f, rhs: f <$> m}
-    - 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}
+    - warn: {lhs: traverse_ id, rhs: sequenceA_}
 
 
     # LIST COMP
@@ -496,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
 
@@ -511,6 +676,11 @@
 
     - warn: {lhs: fst (unzip x), rhs: map fst x}
     - 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
 
@@ -518,12 +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 ==), 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, 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}
@@ -533,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}
@@ -543,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}
-    - hint: {lhs: maybe (f x) (f . g), rhs: f . maybe x g, note: IncreasesLaziness, name: Too strict maybe}
-    - hint: {lhs: maybe (f x) f y, rhs: f (Data.Maybe.fromMaybe x y), note: IncreasesLaziness, name: Too strict maybe}
+    - 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
 
@@ -593,6 +812,10 @@
     - hint: {lhs: 0 == rem n 2, rhs: even n}
     - hint: {lhs: rem n 2 /= 0, rhs: odd n}
     - hint: {lhs: 0 /= rem n 2, rhs: odd n}
+    - hint: {lhs: mod n 2 == 0, rhs: even n}
+    - hint: {lhs: 0 == mod n 2, rhs: even n}
+    - hint: {lhs: mod n 2 /= 0, rhs: odd n}
+    - hint: {lhs: 0 /= mod n 2, rhs: odd n}
     - hint: {lhs: not (even x), rhs: odd x}
     - hint: {lhs: not (odd x), rhs: even x}
     - hint: {lhs: x ** 0.5, rhs: sqrt x}
@@ -602,7 +825,10 @@
     # CONCURRENT
 
     - hint: {lhs: mapM_ (writeChan a), rhs: writeList2Chan a}
-    - error: {lhs: atomically (readTVar x), rhs: readTVarIO x}
+    - 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
 
@@ -643,17 +869,89 @@
 
     # 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}
 
@@ -671,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}
@@ -681,21 +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
 
@@ -711,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}
@@ -731,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}
@@ -742,6 +1075,12 @@
     - warn: {lhs: null x  , rhs: "False", side: isTuple x, name: Using null on tuple}
     - warn: {lhs: length x, rhs: "1"    , side: isTuple x, name: Using length on tuple}
 
+    # MAP
+
+    - warn: {lhs: "Data.Map.fromList []", rhs: Data.Map.empty}
+    - warn: {lhs: "Data.Map.Lazy.fromList []", rhs: Data.Map.Lazy.empty}
+    - warn: {lhs: "Data.Map.Strict.fromList []", rhs: Data.Map.Strict.empty}
+
 - group:
     name: lens
     enabled: true
@@ -752,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)"}
 
@@ -762,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
@@ -777,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:
@@ -787,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:
@@ -796,7 +1161,10 @@
     - 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:
     name: generalise-for-conciseness
@@ -808,11 +1176,10 @@
     - warn: {lhs: maybe False, rhs: any}
     - warn: {lhs: maybe True, rhs: all}
     - warn: {lhs: either (const mempty), rhs: foldMap}
+    - warn: {lhs: either mempty, rhs: foldMap}
     - warn: {lhs: either (const False), rhs: any}
     - warn: {lhs: either (const True), rhs: all}
     - warn: {lhs: Data.Maybe.fromMaybe mempty, rhs: Data.Foldable.fold}
-    - warn: {lhs: Data.Maybe.fromMaybe False, rhs: or}
-    - warn: {lhs: Data.Maybe.fromMaybe True, rhs: and}
     - warn: {lhs: Data.Maybe.fromMaybe 0, rhs: sum}
     - warn: {lhs: Data.Maybe.fromMaybe 1, rhs: product}
     - warn: {lhs: Data.Maybe.fromMaybe empty, rhs: Data.Foldable.asum}
@@ -830,8 +1197,211 @@
     - hint: {lhs: fromRight (pure ()), rhs: sequenceA_, note: IncreasesLaziness}
     - hint: {lhs: "[fst x, snd x]", rhs: Data.Bifoldable.biList x}
     - 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: 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"}
+    - warn: {lhs: "maybeM (return ()) b a", rhs: "whenJustM a b"}
+    - warn: {lhs: "if a then Just <$> b else pure Nothing", rhs: "whenMaybe a b"}
+    - warn: {lhs: "maybe a b =<< c", rhs: "maybeM a b c"}
+    - 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 >> 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"}
+    - warn: {lhs: "atomicModifyIORef a (\\ v -> (b v, ()))", rhs: "atomicModifyIORef_ a b"}
+    - warn: {lhs: "atomicModifyIORef' a (\\ v -> (b v, ()))", rhs: "atomicModifyIORef'_ a b"}
+    - warn: {lhs: "null (intersect a b)", rhs: "disjoint a b"}
+    - warn: {lhs: "[minBound .. maxBound]", rhs: "enumerate"}
+    - warn: {lhs: "zipWithFrom (,)", rhs: "zipFrom"}
+    - warn: {lhs: "zip [i..]", rhs: "zipFrom i"}
+    - warn: {lhs: "zipWith f [i..]", rhs: "zipWithFrom f i"}
+    - warn: {lhs: "dropWhile isSpace", rhs: "trimStart"}
+    - warn: {lhs: "dropWhileEnd isSpace", rhs: "trimEnd"}
+    - warn: {lhs: "trimEnd (trimStart a)", rhs: "trim a"}
+    - warn: {lhs: "map toLower", rhs: "lower"}
+    - 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"}
+    - warn: {lhs: "nubSortBy compare", rhs: "nubSort"}
+    - warn: {lhs: "nubSortBy (compare `on` a)", rhs: "nubSortOn a"}
+    - 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: "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:
+    name: future
+    enabled: false
+    rules:
+    - warn: {lhs: return, rhs: pure}
+
+- group:
     name: dollar
     enabled: false
     imports:
@@ -840,6 +1410,42 @@
     - warn: {lhs: a $ b $ c, rhs: a . b $ c}
 
 - group:
+    # These hints are same if all matched functions are monomorphic, or polymorphic, but don't have adhoc polymorphism
+    name: monomorphic
+    enabled: false
+    imports:
+    - package base
+    rules:
+    - warn: {lhs: if c then f x else f y, rhs: f (if c then x else y), note: IncreasesLaziness, name: Too strict if}
+    - hint: {lhs: maybe (f x) (f . g), rhs: f . maybe x g, note: IncreasesLaziness, name: Too strict maybe}
+    - hint: {lhs: maybe (f x) f y, rhs: f (Data.Maybe.fromMaybe x y), note: IncreasesLaziness, name: Too strict maybe}
+
+- group:
+    name: codeworld
+    enabled: false
+    imports:
+    - 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}
+    - hint: {lhs: foldl (&) blank, rhs: pictures}
+    - hint: {lhs: foldl' (&) blank, rhs: pictures}
+    - hint: {lhs: foldr' (&) blank, rhs: pictures}
+    - hint: {lhs: foldr (&) blank, rhs: pictures}
+    - hint: {lhs: foldr1 (&), rhs: pictures}
+    - hint: {lhs: scaled x x, rhs: dilated x}
+    - hint: {lhs: scaledPoint x x, rhs: dilatedPoint x}
+    - warn: {lhs: "brighter (- a)", rhs: "duller a"}
+    - 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
     enabled: false
     imports:
@@ -849,7 +1455,22 @@
     - 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, 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}
+    - warn: {lhs: foldl1 f (reverse x), rhs: foldr1 (flip f) x, note: IncreasesLaziness, name: Use right fold instead of left fold}
+    - warn: {lhs: foldr' f c (reverse x), rhs: foldl' (flip f) c 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}
 
+- group:
+    # used for tests, enabled when testing this file
+    name: testing
+    enabled: false
+    rules:
+    - warn: {lhs: "[issue766| |]", rhs: "mempty", name: "Use mempty"}
+
 # <TEST>
 # yes = concat . map f -- concatMap f
 # yes = foo . bar . concat . map f . baz . bar -- concatMap f . baz . bar
@@ -858,6 +1479,7 @@
 # yes = f x where f x = concat . map head -- concatMap head
 # yes = concat . map f . g -- concatMap f . g
 # yes = concat $ map f x -- concatMap f x
+# yes = map f x & concat -- concatMap f x
 # yes = "test" ++ concatMap (' ':) ["of","this"] -- unwords ("test":["of","this"])
 # yes = if f a then True else b -- f a || b
 # yes = not (a == b) -- a /= b
@@ -868,9 +1490,9 @@
 # yes = not . (/= a) -- (== a)
 # yes = if a then 1 else if b then 1 else 2 -- if a || b then 1 else 2
 # no  = if a then 1 else if b then 3 else 2
-# yes = a >>= return . bob -- bob <$> a
+# yes = a >>= return . bob -- a Data.Functor.<&> bob
 # yes = return . bob =<< a -- bob <$> a
-# yes = m alice >>= pure . b -- b <$> m alice
+# yes = m alice >>= pure . b -- m alice Data.Functor.<&> b
 # yes = pure .b =<< m alice -- b <$> m alice
 # yes = asciiCI "hi" *> pure Hi -- asciiCI "hi" Data.Functor.$> Hi
 # yes = asciiCI "bye" *> return Bye -- asciiCI "bye" Data.Functor.$> Bye
@@ -902,6 +1524,8 @@
 # no  = foo $ \(a, b) -> (a, a + b)
 # yes = map (uncurry (+)) $ zip [1 .. 5] [6 .. 10] -- zipWith (curry (uncurry (+))) [1 .. 5] [6 .. 10]
 # yes = curry (uncurry (+)) -- (+)
+# yes = fst foo .= snd foo -- uncurry (.=) foo
+# yes = fst foo `_ba__'r''` snd foo -- uncurry _ba__'r'' foo
 # no = do iter <- textBufferGetTextIter tb ; textBufferSelectRange tb iter iter
 # no = flip f x $ \y -> y*y+y
 # no = \x -> f x (g x)
@@ -915,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
@@ -927,6 +1554,7 @@
 # yes = if p then s else return () -- Control.Monad.when p s
 # warn = a $$$$ b $$$$ c ==> a . b $$$$$ c
 # yes = when (not . null $ asdf) -- unless (null asdf)
+# yes = (foo . bar . when) (not . null $ asdf) -- (foo . bar) (unless (null asdf))
 # yes = id 1 -- 1
 # yes = case concat (map f x) of [] -> [] -- concatMap f x
 # yes = [v | v <- xs] -- xs
@@ -935,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
@@ -946,10 +1576,13 @@
 # test = \ a -> f a >>= \ b -> return (a, b)
 # fooer input = catMaybes . map Just $ input -- mapMaybe Just
 # yes = mapMaybe id -- catMaybes
+# foo = magic . isLeft $ fmap f x -- magic (isLeft x)
+# foo = (bar . baz . magic . isRight) (fmap f x) -- (bar . baz . magic) (isRight x)
 # main = print $ map (\_->5) [2,3,5] -- const 5
 # main = head $ drop n x -- x !! max 0 n
 # main = head $ drop (-3) x -- x
 # main = head $ drop 2 x -- x !! 2
+# main = foo . bar . baz . head $ drop 2 x -- (foo . bar . baz) (x !! 2)
 # main = drop 0 x -- x
 # main = take 0 x -- []
 # main = take (-5) x -- []
@@ -963,7 +1596,7 @@
 # pairs (x:xs) = map (x,) xs ++ pairs xs
 # {-# ANN foo "HLint: ignore" #-};foo = map f (map g x) -- @Ignore ???
 # {-# HLINT ignore foo #-};foo = map f (map g x) -- @Ignore ???
-# yes = fmap lines $ abc 123 -- lines Control.Applicative.<$> abc 123
+# yes = fmap lines $ abc 123 -- lines <$> abc 123
 # no = fmap lines $ abc $ def 123
 # test = foo . not . not -- id
 # test = map (not . not) xs -- id
@@ -984,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)
@@ -992,7 +1627,8 @@
 # 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 (\ x -> SymInfo x ty) [0 ..]
+# 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
 # {-# LANGUAGE TypeApplications #-} \
@@ -1005,10 +1641,20 @@
 # no = foo $ (,) x $ do {this is a test; and another test}
 # no = sequence (return x)
 # no = sequenceA (pure a)
+# yes = zipWith func xs ys & sequenceA -- Control.Monad.zipWithM func xs ys
 # {-# LANGUAGE QuasiQuotes #-}; no = f (\url -> [hamlet|foo @{url}|])
 # yes = f ((,) x) -- (x,)
+# yes = f ((,) (2 + 3)) -- (2 + 3,)
 # instance Class X where method = map f (map g x) -- map (f . g) x
 # instance Eq X where x == y = compare x y == EQ
+# issue1055 = map f ((sort . map g) xs)
+# issue1049 = True `elem` xs -- or xs
+# issue1049 = elem True -- or
+# issue1062 = bar (\(f, x) -> baz () () . f $ x) -- uncurry ((.) (baz () ()))
+# 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
@@ -1037,12 +1683,43 @@
 # 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)
 # {-# RULES "Id-fmap-id" forall (x :: Id a). fmap id x = x #-}
+# import Data.Map (fromList) \
+# fromList [] -- Data.Map.empty
+# import Data.Map.Lazy (fromList) \
+# fromList [] -- Data.Map.Lazy.empty
+# import Data.Map.Strict (fromList) \
+# fromList [] -- Data.Map.Strict.empty
+# test953 = for [] $ \n -> bar n >>= \case {Just n  -> pure (); Nothing -> baz n}
+# f = map (flip (,) "a") "123" -- (,"a")
+# test1196 = map (flip (,) (+ 1)) "123" -- (,(+ 1))
+# f = map ((,) "a") "123" -- ("a",)
+# test1196 = map ((,) (+ 1)) "123" -- ((+ 1),)
+# test979 = flip Map.traverseWithKey blocks \k v -> lots_of_code_goes_here
+# infixl 4 <*! \
+# test993 = f =<< g <$> x <*! y
+# {-# LANGUAGE QuasiQuotes #-} \
+# test = [issue766| |] -- mempty
+# {-# LANGUAGE QuasiQuotes #-} \
+# test = [issue766| x |]
 # </TEST>
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
new file mode 100644
--- /dev/null
+++ b/data/test-restrict.yaml
@@ -0,0 +1,19 @@
+- 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: []}
+  - {name: restrictedMessage, within: [], message: "Custom message"}
+
+- extensions:
+  - {name: DeriveFunctor, within: []}
+  - {name: DeriveTraversable, within: [], message: "Custom message"}
+
+
+# Test https://github.com/ndmitchell/hlint/issues/766
+- warn:
+    lhs: "[hamlet| |]"
+    rhs: "mempty"
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:            2.2.11
+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-2020
+copyright:          Neil Mitchell 2006-2025
 synopsis:           Source code suggestions
 description:
     HLint gives suggestions on how to improve your source code.
@@ -24,10 +24,19 @@
     hlint.ghci
     HLint_QuickCheck.hs
     HLint_TypeCheck.hs
+extra-source-files:
+    .hlint.yaml
+    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.8.1, GHC==8.6.5, GHC==8.4.4
+tested-with:        GHC==9.12, GHC==9.10, GHC==9.8
 
 source-repository head
     type:     git
@@ -44,10 +53,15 @@
     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
 
+flag hsyaml
+  default: False
+  manual: True
+  description: Use HsYAML instead of yaml
+
 library
     default-language:   Haskell2010
     build-depends:
@@ -58,41 +72,48 @@
         utf8-string,
         data-default >= 0.3,
         cpphs >= 1.20.1,
-        cmdargs >= 0.10,
-        yaml >= 0.5.0,
-        haskell-src-exts >= 1.21 && < 1.24,
-        haskell-src-exts-util >= 0.2.5,
+        cmdargs >= 0.10.22,
         uniplate >= 1.5,
-        ansi-terminal >= 0.6.2,
-        extra >= 1.6.6,
+        ansi-terminal >= 0.8.1,
+        extra >= 1.7.3,
         refact >= 0.3,
-        aeson >= 1.1.2.0,
-        filepattern >= 0.1.1,
-        ghc-lib-parser-ex == 8.8.5.*
-    if !flag(ghc-lib) && impl(ghc >= 8.8.0) && impl(ghc < 8.9.0)
-        build-depends:
-          ghc == 8.8.*,
-          ghc-boot-th,
-          ghc-boot
+        aeson >= 1.3,
+        deriving-aeson >= 0.2,
+        filepattern >= 0.1.1
+
+    if !flag(ghc-lib) && impl(ghc >= 9.12.1) && impl(ghc < 9.13.0)
+      build-depends:
+        ghc == 9.12.*,
+        ghc-boot-th,
+        ghc-boot
     else
-        build-depends:
-          ghc-lib-parser == 8.8.*
+      build-depends:
+          ghc-lib-parser == 9.12.*
+    build-depends:
+        ghc-lib-parser-ex >= 9.12.0.0 && < 9.13.0
 
     if flag(gpl)
         build-depends: hscolour >= 1.21
     else
         cpp-options: -DGPL_SCARES_ME
 
+    if flag(hsyaml)
+        build-depends:
+          HsYAML >= 0.2,
+          HsYAML-aeson >= 0.2
+        cpp-options: -DHS_YAML
+    else
+        build-depends: yaml >= 0.5.0
+
     hs-source-dirs:     src
     exposed-modules:
         Language.Haskell.HLint
-        Language.Haskell.HLint3
-        Language.Haskell.HLint4
     other-modules:
         Paths_hlint
         Apply
         CmdLine
-        Grep
+        Extension
+        Fixity
         HLint
         HsColour
         Idea
@@ -103,41 +124,34 @@
         Timing
         CC
         EmbedData
+        SARIF
+        Summary
         Config.Compute
         Config.Haskell
         Config.Read
         Config.Type
         Config.Yaml
 
+        GHC.All
         GHC.Util
         GHC.Util.ApiAnnotation
         GHC.Util.View
         GHC.Util.Brackets
+        GHC.Util.DynFlags
         GHC.Util.FreeVars
         GHC.Util.HsDecl
         GHC.Util.HsExpr
-        GHC.Util.HsType
-        GHC.Util.Pat
-        GHC.Util.LanguageExtensions.Type
-        GHC.Util.Module
-        GHC.Util.Outputable
         GHC.Util.SrcLoc
-        GHC.Util.DynFlags
-        GHC.Util.RdrName
         GHC.Util.Scope
         GHC.Util.Unify
 
-        HSE.All
-        HSE.Match
-        HSE.Scope
-        HSE.Type
-        HSE.Util
         Hint.All
         Hint.Bracket
         Hint.Comment
         Hint.Duplicate
         Hint.Export
         Hint.Extensions
+        Hint.Fixities
         Hint.Import
         Hint.Lambda
         Hint.List
@@ -145,6 +159,7 @@
         Hint.Match
         Hint.Monad
         Hint.Naming
+        Hint.Negation
         Hint.NewType
         Hint.Pattern
         Hint.Pragma
@@ -152,13 +167,12 @@
         Hint.Smell
         Hint.Type
         Hint.Unsafe
-        Hint.Util
+        Hint.NumLiteral
         Test.All
         Test.Annotations
         Test.InputOutput
-        Test.Proof
-        Test.Translate
         Test.Util
+    ghc-options: -Wunused-binds -Wunused-imports -Worphans -Wprepositive-qualified-module
 
 
 executable hlint
@@ -166,6 +180,8 @@
     build-depends:      base, hlint
     main-is:            src/Main.hs
 
-    ghc-options:        -rtsopts
+    -- See https://github.com/ndmitchell/hlint/pull/1169 for benchmarks
+    -- that indicate -A32 is a good idea
+    ghc-options:        -rtsopts -with-rtsopts=-A32m
     if flag(threaded)
         ghc-options:    -threaded
diff --git a/src/Apply.hs b/src/Apply.hs
--- a/src/Apply.hs
+++ b/src/Apply.hs
@@ -1,11 +1,13 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 
 module Apply(applyHints, applyHintFile, applyHintFiles) where
 
 import Control.Applicative
 import Data.Monoid
-import HSE.All
+import GHC.All
 import Hint.All
 import GHC.Util
+import Data.Generics.Uniplate.DataOnly
 import Idea
 import Data.Tuple.Extra
 import Data.Either
@@ -14,26 +16,29 @@
 import Data.Ord
 import Config.Type
 import Config.Haskell
-import HsSyn
-import qualified SrcLoc as GHC
-import qualified Data.HashSet as Set
+import GHC.Types.SrcLoc
+import GHC.Hs hiding (comments)
+import Language.Haskell.GhclibParserEx.GHC.Hs
+import Data.HashSet qualified as Set
 import Prelude
+import Util
+import Timing
 
 
 -- | Apply hints to a single file, you may have the contents of the file.
 applyHintFile :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO [Idea]
 applyHintFile flags s file src = do
     res <- parseModuleApply flags s file src
-    return $ case res of
+    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
-    return $ 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.
@@ -48,32 +53,28 @@
 
 applyHintsReal :: [Setting] -> Hint -> [ModuleEx] -> [Idea]
 applyHintsReal settings hints_ ms = concat $
-    [ map (classify classifiers . removeRequiresExtensionNotes (hseModule m)) $
+    [ map (classify classifiers . removeRequiresExtensionNotes m) $
         order [] (hintModule hints settings nm m) `merge`
-        concat [order [fromNamed d] $ decHints d | d <- moduleDecls (hseModule m)] `merge`
-        concat [order (maybeToList $ declName d) $ decHints' d | d <- hsmodDecls $ GHC.unLoc $ ghcModule m]
-    | (nm, m) <- mns
-    , let classifiers = cls ++ mapMaybe readPragma (universeBi (hseModule m)) ++ concatMap readComment (ghcComments m)
+        concat [order (maybeToList $ declName d) $ decHints d | d <- hsmodDecls $ unLoc $ ghcModule m]
+    | (nm,m) <- mns
+    , 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
-    , (nm',m') <- mns'
-    , let decHints' = hintDecl' hints settings nm' m' -- partially apply
-    , let order n = map (\i -> i{ideaModule= f $ moduleName (hseModule 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 (/= "")
         cls = [x | SettingClassify x <- settings]
-        mns = map (\x -> (scopeCreate (hseModule x), x)) ms
-        mns' = map (\x -> (scopeCreate' (GHC.unLoc $ ghcModule x), x)) ms
+        mns = map (\x -> (scopeCreate (unLoc $ ghcModule x), x)) ms
         hints = (if length ms <= 1 then noModules else id) hints_
         noModules h = h{hintModules = \_ _ -> []} `mappend` mempty{hintModule = \s a b -> hintModules h s [(a,b)]}
 
 -- If the hint has said you RequiresExtension Foo, but Foo is enabled, drop the note
-removeRequiresExtensionNotes :: Module_ -> Idea -> Idea
+removeRequiresExtensionNotes :: ModuleEx -> Idea -> Idea
 removeRequiresExtensionNotes m = \x -> x{ideaNote = filter keep $ ideaNote x}
     where
-        exts = Set.fromList $ map fromNamed $ moduleExtensions m
+        exts = Set.fromList $ concatMap snd $ languagePragmas $ pragmas (comments (hsmodAnn (hsmodExt . unLoc . ghcModule $ m)))
         keep (RequiresExtension x) = not $ x `Set.member` exts
         keep _ = True
 
@@ -87,11 +88,25 @@
 parseModuleApply flags s file src = do
     res <- parseModuleEx (parseFlagsAddFixities [x | Infix x <- s] flags) file src
     case res of
-      Right r -> return $ Right r
+      Right r -> pure $ Right r
       Left (ParseError sl msg ctxt) ->
-            return $ Left $ classify [x | SettingClassify x <- s] $ rawIdeaN Error "Parse error" (mkSrcSpan sl sl) ctxt Nothing []
+            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 (
+            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
+
+
 -- | Find which hints a list of settings implies.
 allHints :: [Setting] -> Hint
 allHints xs = mconcat $ hintRules [x | SettingMatchExp x <- xs] : map f builtin
@@ -101,10 +116,11 @@
 
 -- | Given some settings, make sure the severity field of the Idea is correct.
 classify :: [Classify] -> Idea -> Idea
-classify xs i =  let s = foldl' (f i) (ideaSeverity i) xs in s `seq` i{ideaSeverity=s}
+classify xs i = let s = foldl' (f i) (ideaSeverity i) xs in s `seq` i{ideaSeverity=s}
     where
         -- figure out if we need to change the severity
         f :: Idea -> Severity -> Classify -> Severity
-        f i r c | classifyHint c ~= [ideaHint i] && classifyModule c ~= ideaModule i && classifyDecl c ~= ideaDecl i = classifySeverity c
+        f i r c | classifyHint c ~~= ideaHint i && classifyModule c ~= ideaModule i && classifyDecl c ~= ideaDecl i = classifySeverity c
                 | otherwise = r
-        x ~= y = null x || x `elem` y
+        x ~= y = x == "" || any (wildcardMatch x) y
+        x  ~~= y = x == "" || x == y || ((x ++ ":") `isPrefixOf` y)
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 #-}
 -- |
@@ -13,13 +14,15 @@
 import Data.Aeson (ToJSON(..), (.=), encode, object)
 import Data.Char (toUpper)
 import Data.Text (Text)
-import Language.Haskell.Exts.SrcLoc (SrcSpan(..))
 
-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 GHC.Types.SrcLoc qualified as GHC
+import GHC.Util qualified as GHC
+
 data Issue = Issue
     { issueType :: Text
     , issueCheckName :: Text
@@ -119,11 +122,11 @@
     points Warning = 5 * basePoints
     points Error = 10 * basePoints
 
-fromSrcSpan :: SrcSpan -> Location
-fromSrcSpan SrcSpan{..} = Location
+fromSrcSpan :: GHC.SrcSpan -> Location
+fromSrcSpan GHC.SrcSpan{..} = Location
     (locationFileName srcSpanFilename)
-    (Position srcSpanStartLine srcSpanStartColumn)
-    (Position srcSpanEndLine srcSpanEndColumn)
+    (Position srcSpanStartLine' srcSpanStartColumn)
+    (Position srcSpanEndLine' srcSpanEndColumn)
   where
     locationFileName ('.':'/':x) = x
     locationFileName x = x
diff --git a/src/CmdLine.hs b/src/CmdLine.hs
--- a/src/CmdLine.hs
+++ b/src/CmdLine.hs
@@ -1,5 +1,6 @@
+{-# LANGUAGE ImportQualifiedPost, CPP #-}
 {-# LANGUAGE PatternGuards, DeriveDataTypeable, TupleSections #-}
-{-# OPTIONS_GHC -fno-warn-missing-fields -fno-cse -O0 #-}
+{-# OPTIONS_GHC -Wno-missing-fields -fno-cse -O0 #-}
 
 module CmdLine(
     Cmd(..), getCmd,
@@ -8,33 +9,39 @@
     ) where
 
 import Control.Monad.Extra
-import qualified Data.ByteString as BS
+import Control.Exception.Extra
+import Data.ByteString qualified as BS
 import Data.Char
-import Data.List
+import Data.List.NonEmpty qualified as NE
+import Data.List.Extra
 import Data.Maybe
 import Data.Functor
-import HSE.All(CppFlags(..))
-import Language.Haskell.Exts(defaultParseMode, baseLanguage)
-import Language.Haskell.Exts.Extension
+import GHC.All(CppFlags(..))
+import GHC.LanguageExtensions.Type
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx
+import GHC.Driver.Session hiding (verbosity)
+
 import Language.Preprocessor.Cpphs
 import System.Console.ANSI(hSupportsANSI)
 import System.Console.CmdArgs.Explicit(helpText, HelpFormat(..))
 import System.Console.CmdArgs.Implicit
 import System.Directory.Extra
-import System.Environment.Extra
+import System.Environment
 import System.Exit
 import System.FilePath
 import System.IO
 import System.IO.Error
-import System.Info.Extra
 import System.Process
 import System.FilePattern
 
 import EmbedData
 import Util
+import Timing
+import Extension
 import Paths_hlint
 import Data.Version
 import Prelude
+import Config.Type (Severity (Warning))
 
 
 getCmd :: [String] -> IO Cmd
@@ -43,33 +50,30 @@
 
 
 automatic :: Cmd -> IO Cmd
-automatic cmd = case cmd of
-    CmdMain{} -> dataDir =<< path =<< git =<< extension cmd
-    CmdGrep{} -> path =<< extension cmd
-    CmdTest{} -> dataDir cmd
-    _ -> return cmd
+automatic cmd = dataDir =<< path =<< git =<< extension cmd
     where
-        path cmd = return $ if null $ cmdPath cmd then cmd{cmdPath=["."]} else cmd
-        extension cmd = return $ if null $ cmdExtension cmd then cmd{cmdExtension=["hs","lhs"]} else cmd
+        path cmd = pure $ if null $ cmdPath cmd then cmd{cmdPath=["."]} else cmd
+        extension cmd = pure $ if null $ cmdExtension cmd then cmd{cmdExtension=["hs","lhs"]} else cmd
         dataDir cmd
-            | cmdDataDir cmd  /= "" = return cmd
+            | cmdDataDir cmd  /= "" = pure cmd
             | otherwise = do
                 x <- getDataDir
                 b <- doesDirectoryExist x
-                if b then return cmd{cmdDataDir=x} else do
+                if b then pure cmd{cmdDataDir=x} else do
                     exe <- getExecutablePath
-                    return cmd{cmdDataDir = takeDirectory exe </> "data"}
+                    pure cmd{cmdDataDir = takeDirectory exe </> "data"}
         git cmd
             | cmdGit cmd = do
                 mgit <- findExecutable "git"
                 case mgit of
-                    Nothing -> error "Could not find git"
+                    Nothing -> errorIO "Could not find git"
                     Just git -> do
                         let args = ["ls-files", "--cached", "--others", "--exclude-standard"] ++
                                    map ("*." ++) (cmdExtension cmd)
-                        files <- readProcess git args ""
-                        return cmd{cmdFiles = cmdFiles cmd ++ lines files}
-            | otherwise = return cmd
+                        files <- timedIO "Execute" (unwords $ git:args) $
+                            readProcess git args ""
+                        pure cmd{cmdFiles = cmdFiles cmd ++ lines files}
+            | otherwise = pure cmd
 
 
 exitWithHelp :: IO a
@@ -82,26 +86,26 @@
 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)
 
 
 instance Default ColorMode where
-  def = if isWindows then Never else Auto
+  def = Auto
 
 
 data Cmd
     = 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
-        ,cmdWithHints :: [String]        -- ^ hints that are given on the command line
+        ,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,32 +130,10 @@
         ,cmdRefactorOptions :: String   -- ^ Options to pass to the `refactor` executable.
         ,cmdWithRefactor :: FilePath    -- ^ Path to refactor tool
         ,cmdIgnoreGlob :: [FilePattern]
-        }
-    | CmdGrep
-        {cmdFiles :: [FilePath]    -- ^ which files to run it on, nothing = none given
-        ,cmdPattern :: String
-        ,cmdExtension :: [String]        -- ^ extensions
-        ,cmdLanguage :: [String]      -- ^ the extensions (may be prefixed by "No")
-        ,cmdPath :: [String]
-        ,cmdCppDefine :: [String]
-        ,cmdCppInclude :: [FilePath]
-        ,cmdCppFile :: [FilePath]
-        ,cmdCppSimple :: Bool
-        ,cmdCppAnsi :: Bool
-        }
-    | CmdTest
-        {cmdProof :: [FilePath]          -- ^ a proof script to check against
-        ,cmdGivenHints :: [FilePath]     -- ^ which settings files were explicitly given
-        ,cmdDataDir :: FilePath          -- ^ the data directory
-        ,cmdReports :: [FilePath]        -- ^ where to generate reports
-        ,cmdWithHints :: [String]        -- ^ hints that are given on the command line
-        ,cmdTempDir :: FilePath          -- ^ temporary directory to put the files in
-        ,cmdQuickCheck :: Bool
-        ,cmdTypeCheck :: Bool
-        }
-    | CmdHSE
-        {cmdFiles :: [FilePath]
-        ,cmdLanguage :: [String]      -- ^ the extensions (may be prefixed by "No")
+        ,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)
 
@@ -159,13 +142,13 @@
         {cmdFiles = def &= args &= typ "FILE/DIR"
         ,cmdReports = nam "report" &= opt "report.html" &= typFile &= help "Generate a report in HTML"
         ,cmdGivenHints = nam "hint" &= typFile &= help "Hint/ignore file to use"
-        ,cmdWithHints = nam "with" &= typ "HINT" &= help "Extra hints to use"
         ,cmdWithGroups = nam_ "with-group" &= typ "GROUP" &= help "Extra hint groups to use"
         ,cmdGit = nam "git" &= help "Run on files tracked by git"
-        ,cmdColor = nam "colour" &= name "color" &= opt Always &= typ "always/never/auto" &= help "Color output (requires ANSI terminal; auto means on when $TERM is supported; by itself, selects always)"
+        ,cmdColor = nam "colour" &= name "color" &= opt Always &= typ "always/never/auto" &= help "Color output (requires an ANSI terminal; 'auto' means on if the standard output channel can support ANSI; by itself, selects 'always')"
         ,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"
@@ -180,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"
@@ -188,78 +172,75 @@
         ,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"
+        ,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"
-    ,CmdGrep
-        {cmdFiles = def &= args &= typ "FILE/DIR"
-        ,cmdPattern = def &= argPos 0 &= typ "PATTERN"
-        } &= explicit &= name "grep"
-    ,CmdTest
-        {cmdProof = nam_ "proof" &= typFile &= help "Isabelle/HOLCF theory file"
-        ,cmdTypeCheck = nam_ "typecheck" &= help "Use GHC to type check the hints"
-        ,cmdQuickCheck = nam_ "quickcheck" &= help "Use QuickCheck to check the hints"
-        ,cmdTempDir = nam_ "tempdir" &= help "Where to put temporary files (not cleaned up)"
-        } &= explicit &= name "test"
         &= details ["HLint gives hints on how to improve Haskell code."
                    ,""
                    ,"To check all Haskell files in 'src' and generate a report type:"
                    ,"  hlint src --report"]
-    ,CmdHSE
-        {} &= explicit &= name "hse"
     ] &= program "hlint" &= verbosity
-    &=  summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2020")
+    &=  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?
---   * If someone passes cmdWithHints, only look at files they explicitly request
---   * If someone passes an explicit hint name, automatically merge in data/hlint.yaml
+--   Either we use the implicit search, or we follow the cmdGivenHints
 --   We want more important hints to go last, since they override
 cmdHintFiles :: Cmd -> IO [(FilePath, Maybe String)]
 cmdHintFiles cmd = do
-    let explicit1 = [hlintYaml | null $ cmdWithHints cmd]
-    let explicit2 = cmdGivenHints cmd
-    bad <- filterM (notM . doesFileExist) explicit2
-    let explicit2' = map (,Nothing) explicit2
+    let explicit = cmdGivenHints cmd
+    bad <- filterM (notM . doesFileExist) explicit
     when (bad /= []) $
         fail $ unlines $ "Failed to find requested hint files:" : map ("  "++) bad
-    if cmdWithHints cmd /= [] then return $ explicit1 ++ explicit2' else do
+
+    -- if the user has given any explicit hints, ignore the local ones
+    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
         -- Ignores home directory when it isn't present.
-        home <- catchIOError ((:[]) <$> getHomeDirectory) (const $ return [])
-        implicit <- findM doesFileExist $
+        home <- catchIOError ((:[]) <$> getHomeDirectory) (const $ pure [])
+        findM doesFileExist $
             map (</> ".hlint.yaml") (ancestors curdir ++ home) -- to match Stylish Haskell
-            ++ ["HLint.hs"] -- the default in HLint 1.*
-        return $ explicit1 ++ map (,Nothing) (maybeToList implicit) ++ explicit2'
+    pure $ hlintYaml : map (,Nothing) (maybeToList implicit ++ explicit)
     where
         ancestors = init . map joinPath . reverse . inits . splitPath
 
-cmdExtensions :: Cmd -> (Language, [Extension])
+cmdExtensions :: Cmd -> (Maybe Language, ([Extension], [Extension]))
 cmdExtensions = getExtensions . cmdLanguage
 
 
 cmdCpp :: Cmd -> CppFlags
 cmdCpp cmd
     | cmdCppSimple cmd = CppSimple
-    | EnableExtension CPP `elem` 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,drop 1 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 -> return True
-  Never  -> return False
-  Auto   -> hSupportsANSI stdout
-
+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
@@ -285,23 +266,25 @@
 getFile :: (FilePath -> Bool) -> [FilePath] -> [String] -> Maybe FilePath -> FilePath -> IO [FilePath]
 getFile _ path _ (Just tmpfile) "-" =
     -- make sure we don't reencode any Unicode
-    BS.getContents >>= BS.writeFile tmpfile >> return [tmpfile]
-getFile _ path _ Nothing "-" = return ["-"]
+    BS.getContents >>= BS.writeFile tmpfile >> pure [tmpfile]
+getFile _ path _ Nothing "-" = pure ["-"]
 getFile _ [] exts _ file = exitMessage $ "Couldn't find file: " ++ file
 getFile ignore (p:ath) exts t file = do
     isDir <- doesDirectoryExist $ p <\> file
     if isDir then do
-        let avoidDir x = let y = takeFileName x in "_" `isPrefixOf` y || ("." `isPrefixOf` y && not (all (== '.') y))
+        let ignoredDirectories = ["dist", "dist-newstyle"]
+            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 (return . not . avoidDir) $ p <\> file
-        return [x | x <- xs, drop 1 (takeExtension x) `elem` exts, not $ avoidFile 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 return [p <\> file]
+        if isFil then
+            pure [p <\> file | not $ ignore $ p <\> file]
          else do
             res <- getModule p exts file
             case res of
-                Just x -> return [x]
+                Just x -> pure [x]
                 Nothing -> getFile ignore ath exts t file
 
 
@@ -313,28 +296,52 @@
         isMod _ = False
         pre = path <\> joinPath xs
 
-        f [] = return Nothing
+        f [] = pure Nothing
         f (x:xs) = do
             let s = pre <.> x
             b <- doesFileExist s
-            if b then return $ Just s else f xs
-getModule _ _ _ = return Nothing
+            if b then pure $ Just s else f xs
+getModule _ _ _ = pure Nothing
 
 
-getExtensions :: [String] -> (Language, [Extension])
-getExtensions args = (lang, foldl f (if null langs then parseExtensions else []) exts)
-    where
-        lang = if null langs then baseLanguage defaultParseMode else fromJust $ lookup (last langs) ls
+getExtensions :: [String] -> (Maybe Language, ([Extension], [Extension]))
+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 <- knownLanguages]
 
-        f a "Haskell98" = []
-        f a ('N':'o':x) | Just x <- readExtension x = delete x a
-        f a x | Just x <- readExtension x = x : delete x a
-        f a x = UnknownExtension x : delete (UnknownExtension x) a
+        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 ++ "'"
 
-readExtension :: String -> Maybe Extension
-readExtension x = case classifyExtension x of
-    UnknownExtension _ -> Nothing
-    x -> Just x
+        deletes :: [Extension] -> [Extension] -> [Extension]
+        deletes [] ys = 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
@@ -1,12 +1,22 @@
+{-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- | Given a file, guess settings from it by looking at the hints.
 module Config.Compute(computeSettings) where
 
-import HSE.All
+import GHC.All
+import GHC.Util
 import Config.Type
-import Config.Haskell
-import Data.Monoid
+import Fixity
+import Data.Generics.Uniplate.DataOnly
+import GHC.Hs hiding (Warning)
+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
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import Prelude
 
 
@@ -17,46 +27,55 @@
     x <- parseModuleEx flags file Nothing
     case x of
         Left (ParseError sl msg _) ->
-            return ("# Parse error " ++ showSrcLoc sl ++ ": " ++ msg, [])
-        Right (ModuleEx m _ _ _) -> do
-            let xs = concatMap (findSetting $ UnQual an) (moduleDecls m)
-                r = concatMap (readSetting mempty) xs
-                s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting r ++ ["# no hints found" | null xs]
-            return (s,r)
+            pure ("# Parse error " ++ showSrcSpan sl ++ ": " ++ msg, [])
+        Right ModuleEx{ghcModule=m} -> do
+            let xs = concatMap findSetting (hsmodDecls $ unLoc m)
+                s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting xs ++ ["# no hints found" | null xs]
+            pure (s,xs)
 
+
 renderSetting :: Setting -> [String]
+-- Only need to convert the subset of Setting we generate
 renderSetting (SettingMatchExp HintRule{..}) =
-    ["- warn: {lhs: " ++ show (prettyPrint hintRuleLHS) ++ ", rhs: " ++ show (prettyPrint hintRuleRHS) ++ "}"]
-renderSetting (Infix x) = ["- infix: " ++ show (prettyPrint (toInfixDecl x))]
+    ["- warn: {lhs: " ++ show (unsafePrettyPrint hintRuleLHS) ++ ", rhs: " ++ show (unsafePrettyPrint hintRuleRHS) ++ "}"]
+renderSetting (Infix x) =
+    ["- fixity: " ++ show (unsafePrettyPrint $ toFixitySig x)]
 renderSetting _ = []
 
-findSetting :: (Name S -> QName S) -> Decl_ -> [Decl_]
-findSetting qual (InstDecl _ _ _ (Just xs)) = concatMap (findSetting qual) [x | InsDecl _ x <- xs]
-findSetting qual (PatBind _ (PVar _ name) (UnGuardedRhs _ bod) Nothing) = findExp (qual name) [] bod
-findSetting qual (FunBind _ [InfixMatch _ p1 name ps rhs bind]) = findSetting qual $ FunBind an [Match an name (p1:ps) rhs bind]
-findSetting qual (FunBind _ [Match _ name ps (UnGuardedRhs _ bod) Nothing]) = findExp (qual name) [] $ Lambda an ps bod
-findSetting _ x@InfixDecl{} = [x]
-findSetting _ _ = []
+findSetting :: LocatedA (HsDecl GhcPs) -> [Setting]
+findSetting (L _ (ValD _ x)) = findBind x
+findSetting (L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) =
+    concatMap (findBind . unLoc) cid_binds
+findSetting (L _ (SigD _ (FixSig _ x))) = map Infix $ fromFixitySig x
+findSetting x = []
 
 
--- given a result function name, a list of variables, a body expression, give some hints
-findExp :: QName S -> [String] -> Exp_ -> [Decl_]
-findExp name vs (Lambda _ ps bod) | length ps2 == length ps = findExp name (vs++ps2) bod
-                                  | otherwise = []
-    where ps2 = [x | PVar_ x <- map view ps]
-findExp name vs Var{} = []
-findExp name vs (InfixApp _ x dot y) | isDot dot = findExp name (vs++["_hlint"]) $ App an x $ Paren an $ App an y (toNamed "_hlint")
+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 noAnn LamSingle fun_matches
+findBind _ = []
 
-findExp name vs bod = [PatBind an (toNamed "warn") (UnGuardedRhs an $ InfixApp an lhs (toNamed "==>") rhs) Nothing]
+findExp :: IdP GhcPs -> [String] -> HsExpr GhcPs -> [Setting]
+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 $ 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 = g $ transform f bod
-        rhs = apps $ Var an 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 (toNamed . return) ['a'..]
-        f xx | Var_ x <- view xx, Just y <- lookup x rep = y
-        f (InfixApp _ x dol y) | isDol dol = App an x (paren y)
+        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 $ nlHsPar y
         f x = x
 
-        g o@(InfixApp _ _ _ x) | isAnyApp x || isAtom x = o
-        g o@App{} = o
-        g o = paren o
+
+mkVar :: String -> HsExpr GhcPs
+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,97 +1,56 @@
-{-# LANGUAGE PatternGuards, ViewPatterns, ScopedTypeVariables, TupleSections #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE PatternGuards, ViewPatterns, TupleSections #-}
 module Config.Haskell(
     readPragma,
-    readComment,
-    readSetting,
-    readFileConfigHaskell
+    readComment
     ) where
 
-import HSE.All
 import Data.Char
 import Data.List.Extra
-import Text.Read.Extra(readMaybe)
+import Text.Read
 import Data.Tuple.Extra
 import Data.Maybe
 import Config.Type
 import Util
 import Prelude
 
-import qualified HsSyn as GHC
-import qualified BasicTypes as GHC
 import GHC.Util
-import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
 
-import SrcLoc as GHC
-import ApiAnnotation
-
-
-addInfix :: ParseFlags -> ParseFlags
-addInfix = parseFlagsAddFixities $ infix_ (-1) ["==>"]
-
-
----------------------------------------------------------------------
--- READ A SETTINGS FILE
-
-readFileConfigHaskell :: FilePath -> Maybe String -> IO [Setting]
-readFileConfigHaskell file contents = do
-    let flags = addInfix defaultParseFlags
-    res <- parseModuleEx flags file contents
-    case res of
-        Left (ParseError sl msg err) ->
-            error $ "Config parse failure at " ++ showSrcLoc sl ++ ": " ++ msg ++ "\n" ++ err
-        Right modEx@(ModuleEx m _ _ _) -> return $ readSettings m ++ map SettingClassify (concatMap readComment (ghcComments modEx))
-
-
--- | Given a module containing HLint settings information return the 'Classify' rules and the 'HintRule' expressions.
---   Any fixity declarations will be discarded, but any other unrecognised elements will result in an exception.
-readSettings :: Module_ -> [Setting]
-readSettings m = concatMap (readSetting $ scopeCreate m) $ concatMap getEquations $
-                       [AnnPragma l x | AnnModulePragma l x <- modulePragmas m] ++ moduleDecls m
-
-
-readSetting :: Scope -> Decl_ -> [Setting]
-readSetting s (FunBind _ [Match _ (Ident _ (getSeverity -> Just severity)) pats (UnGuardedRhs _ bod) bind])
-    | InfixApp _ lhs op rhs <- bod, opExp op ~= "==>" =
-        let (a,b) = readSide $ childrenBi bind in
-        let unit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExt [] GHC.Boxed in
-        [SettingMatchExp $
-         HintRule severity (head $ snoc names defaultHintName) s (fromParen lhs) (fromParen rhs) a b
-        -- Todo : Replace these with "proper" GHC expressions.
-         (extendInstances mempty) (extendInstances unit) (extendInstances unit) Nothing]
-    | otherwise = [SettingClassify $ Classify severity n a b | n <- names2, (a,b) <- readFuncs bod]
-    where
-        names = filter (not . null) $ getNames pats bod
-        names2 = ["" | null names] ++ names
-
-readSetting s x | "test" `isPrefixOf` map toLower (fromNamed x) = []
-readSetting s (AnnPragma _ x) | Just y <- readPragma x = [SettingClassify y]
-readSetting s (PatBind an (PVar _ name) bod bind) = readSetting s $ FunBind an [Match an name [] bod bind]
-readSetting s (FunBind an xs) | length xs /= 1 = concatMap (readSetting s . FunBind an . return) xs
-readSetting s (SpliceDecl an (App _ (Var _ x) (Lit _ y))) = readSetting s $ FunBind an [Match an (toNamed $ fromNamed x) [PLit an (Signless an) y] (UnGuardedRhs an $ Lit an $ String an "" "") Nothing]
-readSetting s x@InfixDecl{} = map Infix $ getFixity x
-readSetting s x = errorOn x "bad hint"
+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 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
 
 -- | 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 :: Annotation S -> Maybe Classify
-readPragma o = case o of
-    Ann _ name x -> f (fromNamed name) x
-    TypeAnn _ name x -> f (fromNamed name) x
-    ModuleAnn _ x -> f "" x
+readPragma :: AnnDecl GhcPs -> Maybe Classify
+readPragma (HsAnnotation _ provenance expr) = f expr
     where
-        f name (Lit _ (String _ s _)) | "hlint:" `isPrefixOf` map toLower s =
+        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 o "bad classify pragma"
+                    Nothing -> errorOn expr "bad classify pragma"
                     Just severity -> Just $ Classify severity (trimStart b) "" name
             where (a,b) = break isSpace $ trimStart $ drop 6 s
-        f name (Paren _ x) = f name x
-        f name (ExpTypeSig _ x _) = f name x
-        f _ _ = Nothing
-
+        f (L _ (HsPar _ x)) = f x
+        f (L _ (ExprWithTySig _ x _)) = f x
+        f _ = Nothing
 
-readComment :: GHC.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
@@ -117,60 +76,15 @@
 readComment _ = []
 
 
-readSide :: [Decl_] -> (Maybe Exp_, [Note])
-readSide = foldl f (Nothing,[])
-    where f (Nothing,notes) (PatBind _ PWildCard{} (UnGuardedRhs _ side) Nothing) = (Just side, notes)
-          f (Nothing,notes) (PatBind _ (fromNamed -> "side") (UnGuardedRhs _ side) Nothing) = (Just side, notes)
-          f (side,[]) (PatBind _ (fromNamed -> "note") (UnGuardedRhs _ note) Nothing) = (side,g note)
-          f _ x = errorOn x "bad side condition"
-
-          g (Lit _ (String _ x _)) = [Note x]
-          g (List _ xs) = concatMap g xs
-          g x = case fromApps x of
-              [con -> Just "IncreasesLaziness"] -> [IncreasesLaziness]
-              [con -> Just "DecreasesLaziness"] -> [DecreasesLaziness]
-              [con -> Just "RemovesError",fromString -> Just a] -> [RemovesError a]
-              [con -> Just "ValidInstance",fromString -> Just a,var -> Just b] -> [ValidInstance a b]
-              [con -> Just "RequiresExtension",con -> Just a] -> [RequiresExtension a]
-              _ -> errorOn x "bad note"
-
-          con :: Exp_ -> Maybe String
-          con c@Con{} = Just $ prettyPrint c; con _ = Nothing
-          var c@Var{} = Just $ prettyPrint c; var _ = Nothing
-
-
--- Note: Foo may be ("","Foo") or ("Foo",""), return both
-readFuncs :: Exp_ -> [(String, String)]
-readFuncs (App _ x y) = readFuncs x ++ readFuncs y
-readFuncs (Lit _ (String _ "" _)) = [("","")]
-readFuncs (Var _ (UnQual _ name)) = [("",fromNamed name)]
-readFuncs (Var _ (Qual _ (ModuleName _ mod) name)) = [(mod, fromNamed name)]
-readFuncs (Con _ (UnQual _ name)) = [(fromNamed name,""),("",fromNamed name)]
-readFuncs (Con _ (Qual _ (ModuleName _ mod) name)) = [(mod ++ "." ++ fromNamed name,""),(mod,fromNamed name)]
-readFuncs x = errorOn x "bad classification rule"
-
-
-getNames :: [Pat_] -> Exp_ -> [String]
-getNames ps _ | ps /= [], Just ps <- mapM fromPString ps = ps
-getNames [] (InfixApp _ lhs op rhs) | opExp op ~= "==>" = map ("Use "++) names
-    where
-        lnames = map f $ childrenS lhs
-        rnames = map f $ childrenS rhs
-        names = filter (not . isUnifyVar) $ (rnames \\ lnames) ++ rnames
-        f (Ident _ x) = x
-        f (Symbol _ x) = x
-getNames _ _ = []
-
-
-errorOn :: (Annotated ast, Pretty (ast S)) => ast S -> String -> b
-errorOn val msg = exitMessageImpure $
-    showSrcLoc (getPointLoc $ ann val) ++
+errorOn :: Outputable a => LocatedA a -> String -> b
+errorOn (L pos val) msg = exitMessageImpure $
+    showSrcSpan (locA pos) ++
     ": Error while reading hint file, " ++ msg ++ "\n" ++
-    prettyPrint val
+    unsafePrettyPrint val
 
-errorOnComment :: GHC.Located AnnotationComment -> String -> b
+errorOnComment :: LEpaComment -> String -> b
 errorOnComment c@(L s _) msg = exitMessageImpure $
     let isMultiline = isCommentMultiline c in
-    showSrcLoc (ghcSrcLocToHSE $ GHC.srcSpanStart 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/Read.hs b/src/Config/Read.hs
--- a/src/Config/Read.hs
+++ b/src/Config/Read.hs
@@ -2,7 +2,8 @@
 module Config.Read(readFilesConfig) where
 
 import Config.Type
-import Config.Haskell
+import Control.Monad
+import Control.Exception.Extra
 import Config.Yaml
 import Data.List.Extra
 import System.FilePath
@@ -10,8 +11,11 @@
 
 readFilesConfig :: [(FilePath, Maybe String)] -> IO [Setting]
 readFilesConfig files = do
-        yaml <- mapM (uncurry readFileConfigYaml) yaml
-        haskell <- mapM (uncurry readFileConfigHaskell) haskell
-        return $ concat haskell ++ settingsFromConfigYaml yaml
-    where
-        (yaml, haskell) = partition (\(x,_) -> lower (takeExtension x) `elem` [".yml",".yaml"]) files
+    let (yaml, haskell) = partition (\(x,_) -> lower (takeExtension x) `elem` [".yml",".yaml"]) files
+    unless (null haskell) $
+        errorIO $ "HLint 2.3 and beyond cannot use Haskell configuration files.\n" ++
+                  "Tried to use: " ++ show haskell ++ "\n" ++
+                  "Convert it to .yaml file format, following the example at\n" ++
+                  "  <https://github.com/ndmitchell/hlint/blob/master/data/hlint.yaml>"
+    yaml <- mapM (uncurry readFileConfigYaml) yaml
+    pure $ settingsFromConfigYaml yaml
diff --git a/src/Config/Type.hs b/src/Config/Type.hs
--- a/src/Config/Type.hs
+++ b/src/Config/Type.hs
@@ -1,18 +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 HSE.All
 import Data.Char
 import Data.List.Extra
+import Data.Monoid
 import Prelude
 
-import qualified HsSyn
+
+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
@@ -40,8 +53,9 @@
     = Ignore -- ^ The issue has been explicitly ignored and will usually be hidden (pass @--show@ on the command line to see ignored ideas).
     | 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.
-      deriving (Eq,Ord,Show,Read,Bounded,Enum)
+    | Error -- ^ Available as a setting for the user. Only parse errors have this setting by default.
+      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
@@ -94,28 +108,61 @@
 data HintRule = HintRule
     {hintRuleSeverity :: Severity -- ^ Default severity for the hint.
     ,hintRuleName :: String -- ^ Name for the hint.
-    ,hintRuleScope :: Scope -- ^ Module scope in which the hint operates.
-    ,hintRuleLHS :: Exp SrcSpanInfo -- ^ LHS
-    ,hintRuleRHS :: Exp SrcSpanInfo -- ^ RHS
-    ,hintRuleSide :: Maybe (Exp SrcSpanInfo) -- ^ Side condition, typically specified with @where _ = ...@.
     ,hintRuleNotes :: [Note] -- ^ Notes about application of the hint.
+    ,hintRuleScope :: Scope -- ^ Module scope in which the hint operates (GHC parse tree).
     -- We wrap these GHC elements in 'HsExtendInstances' in order that we may derive 'Show'.
-    ,hintRuleGhcScope :: HsExtendInstances Scope' -- ^ Module scope in which the hint operates (GHC parse tree).
-    ,hintRuleGhcLHS :: HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ LHS (GHC parse tree).
-    ,hintRuleGhcRHS :: HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ RHS (GHC parse tree).
-    ,hintRuleGhcSide :: Maybe (HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs))  -- ^ Side condition (GHC parse tree).
+    ,hintRuleLHS :: HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs) -- ^ LHS (GHC parse tree).
+    ,hintRuleRHS :: HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs) -- ^ RHS (GHC parse tree).
+    ,hintRuleSide :: Maybe (HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs))  -- ^ Side condition (GHC parse tree).
     }
     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
 
@@ -136,5 +183,5 @@
     | SettingArgument String -- ^ Extra command-line argument
     | SettingSmell SmellType Int
     | Builtin String -- use a builtin hint set
-    | Infix Fixity
+    | Infix FixityInfo
       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,48 +1,119 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedStrings, ViewPatterns, RecordWildCards, GeneralizedNewtypeDeriving, TupleSections #-}
 
 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.Yaml
-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 Control.Exception.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 HSE.All hiding (Rule, String)
+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 GHC.Unit.Module
 import Data.Functor
+import Data.Monoid
 import Data.Semigroup
 import Timing
-import Util
 import Prelude
 
-import qualified Lexer as GHC
-import qualified ErrUtils
-import qualified Outputable
-import qualified HsSyn
-import GHC.Util (baseDynFlags, Scope',scopeCreate')
+import GHC.Data.Bag
+import GHC.Parser.Lexer
+import GHC.Utils.Error hiding (Severity)
+import GHC.Hs
+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 AESON == 2
+import Data.Aeson.KeyMap (toHashMapText)
+#endif
 
+#ifdef HS_YAML
+
+import Data.YAML (Pos)
+import Data.YAML.Aeson (encode1Strict, decode1Strict)
+import Data.Aeson hiding (encode)
+import Data.Aeson.Types (Parser)
+import Data.ByteString qualified as BSS
+
+decodeFileEither :: FilePath -> IO (Either (Pos, String) ConfigYaml)
+decodeFileEither path = decode1Strict <$> BSS.readFile path
+
+decodeEither' :: BSS.ByteString -> Either (Pos, String) ConfigYaml
+decodeEither' = decode1Strict
+
+displayException :: (Pos, String) -> String
+displayException = show
+
+encode :: Value -> BSS.ByteString
+encode = encode1Strict
+
+#else
+
+import Data.Yaml
+import Control.Exception.Extra
+
+#endif
+
+#if AESON == 1
+toHashMapText :: a -> a
+toHashMapText = id
+#endif
+
 -- | Read a config file in YAML format. Takes a filename, and optionally the contents.
 --   Fails if the YAML doesn't parse or isn't valid HLint YAML
 readFileConfigYaml :: FilePath -> Maybe String -> IO ConfigYaml
 readFileConfigYaml file contents = timedIO "Config" file $ do
     val <- case contents of
-        Nothing -> decodeFileEither file
-        Just src -> return $ 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 -> return v
+        Right v -> pure v
 
+isBuiltinYaml :: FilePath -> Bool
+isBuiltinYaml = (== "data/hlint.yaml")
 
 ---------------------------------------------------------------------
 -- YAML DATA TYPE
@@ -57,15 +128,13 @@
 
 data Package = Package
     {packageName :: String
-    ,packageModules :: [ImportDecl S]
-    ,packageGhcModules :: [HsExtendInstances (HsSyn.LImportDecl HsSyn.GhcPs)]
+    ,packageModules :: [HsExtendInstances (LImportDecl GhcPs)]
     } deriving Show
 
 data Group = Group
     {groupName :: String
     ,groupEnabled :: Bool
-    ,groupImports :: [Either String (ImportDecl S)] -- Left for package imports
-    ,groupGhcImports :: [Either String (HsExtendInstances (HsSyn.LImportDecl HsSyn.GhcPs))]
+    ,groupImports :: [Either String (HsExtendInstances (LImportDecl GhcPs))]
     ,groupRules :: [Either HintRule Classify] -- HintRule has scope set to mempty
     } deriving Show
 
@@ -95,15 +164,15 @@
     -- 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]
-parseArray v@(getVal -> Array xs) = concatMapM parseArray $ zipWith (\i x -> addVal (show i) x v) [0..] $ V.toList xs
-parseArray v = return [v]
+parseArray v@(getVal -> Array xs) = concatMapM parseArray $ zipWithFrom (\i x -> addVal (show i) x v) 0 $ V.toList xs
+parseArray v = pure [v]
 
 parseObject :: Val -> Parser (Map.HashMap T.Text Value)
-parseObject (getVal -> Object x) = return x
+parseObject (getVal -> Object x) = pure (toHashMapText x)
 parseObject v = parseFail v "Expected an Object"
 
 parseObject1 :: Val -> Parser (String, Val)
@@ -114,7 +183,7 @@
         _ -> parseFail v $ "Expected exactly one key but got " ++ show (Map.size mp)
 
 parseString :: Val -> Parser String
-parseString (getVal -> String x) = return $ T.unpack x
+parseString (getVal -> String x) = pure $ T.unpack x
 parseString v = parseFail v "Expected a String"
 
 parseInt :: Val -> Parser Int
@@ -125,11 +194,17 @@
 parseArrayString = parseArray >=> mapM parseString
 
 maybeParse :: (Val -> Parser a) -> Maybe Val -> Parser (Maybe a)
-maybeParse parseValue Nothing = return Nothing
+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) = return b
+parseBool (getVal -> Bool b) = pure b
 parseBool v = parseFail v "Expected a Bool"
 
 parseField :: String -> Val -> Parser Val
@@ -137,59 +212,63 @@
     x <- parseFieldOpt s v
     case x of
         Nothing -> parseFail v $ "Expected a field named " ++ s
-        Just v -> return v
+        Just v -> pure v
 
 parseFieldOpt :: String -> Val -> Parser (Maybe Val)
 parseFieldOpt s v = do
     mp <- parseObject v
     case Map.lookup (T.pack s) mp of
-        Nothing -> return Nothing
-        Just x -> return $ Just $ addVal s x v
+        Nothing -> pure Nothing
+        Just x -> pure $ Just $ addVal s x v
 
 allowFields :: Val -> [String] -> Parser ()
 allowFields v allow = do
     mp <- parseObject v
     let bad = map T.unpack (Map.keys mp) \\ allow
     when (bad /= []) $
-        parseFail v $ "Not allowed keys: " ++ unwords bad
-
-parseHSE :: (ParseMode -> String -> ParseResult v) -> Val -> Parser v
-parseHSE parser v = do
-    x <- parseString v
-    case parser defaultParseMode{extensions=configExtensions} x of
-        ParseOk x -> return x
-        ParseFailed loc s ->
-          parseFail v $ "Failed to parse " ++ s ++ ", when parsing:\n  " ++ x
+        parseFail v
+          $ "Not allowed keys: " ++ unwords bad
+          ++ ", Allowed keys: " ++ unwords allow
 
-parseGHC :: (ParseMode -> String -> GHC.ParseResult v) -> Val -> Parser v
+parseGHC :: (ParseFlags -> String -> ParseResult v) -> Val -> Parser v
 parseGHC parser v = do
     x <- parseString v
-    case parser defaultParseMode{extensions=configExtensions} x of
-        GHC.POk _ x -> return x
-        GHC.PFailed _ loc err ->
-          let msg = Outputable.showSDoc baseDynFlags $
-                ErrUtils.pprLocErrMsg (ErrUtils.mkPlainErrMsg baseDynFlags loc err)
+    case parser defaultParseFlags{enabledExtensions=configExtensions, disabledExtensions=[]} x of
+        POk _ x -> pure x
+        PFailed ps ->
+          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
-    parseJSON Null = return mempty
-    parseJSON x = parseConfigYaml $ newVal x
+newtype ConfigYamlBuiltin = ConfigYamlBuiltin { getConfigYamlBuiltin :: ConfigYaml }
+  deriving (Semigroup, Monoid)
 
-parseConfigYaml :: Val -> Parser ConfigYaml
-parseConfigYaml v = do
+newtype ConfigYamlUser = ConfigYamlUser { getConfigYamlUser :: ConfigYaml }
+  deriving (Semigroup, Monoid)
+
+instance FromJSON ConfigYamlBuiltin where
+    parseJSON Null = pure mempty
+    parseJSON x = ConfigYamlBuiltin <$> parseConfigYaml True (newVal x)
+
+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"
 
@@ -197,15 +276,14 @@
 parsePackage :: Val -> Parser Package
 parsePackage v = do
     packageName <- parseField "name" v >>= parseString
-    packageModules <- parseField "modules" v >>= parseArray >>= mapM (parseHSE parseImportDeclWithMode)
-    packageGhcModules <- parseField "modules" v >>= parseArray >>= mapM (fmap extendInstances <$> parseGHC parseImportDeclGhcWithMode)
+    packageModules <- parseField "modules" v >>= parseArray >>= mapM (fmap extendInstances <$> parseGHC parseImportDeclGhcWithMode)
     allowFields v ["name","modules"]
-    return Package{..}
+    pure Package{..}
 
 parseFixity :: Val -> Parser [Setting]
-parseFixity v = parseArray v >>= concatMapM (parseHSE parseDeclWithMode >=> f)
+parseFixity v = parseArray v >>= concatMapM (parseGHC parseDeclGhcWithMode >=> f)
     where
-        f x@InfixDecl{} = return $ map Infix $ getFixity x
+        f (L _ (SigD _ (FixSig _ x))) = pure $ map Infix $ fromFixitySig x
         f _ = parseFail v "Expected fixity declaration"
 
 parseSmell :: Val -> Parser [Setting]
@@ -213,59 +291,50 @@
   smellName <- parseField "type" v >>= parseString
   smellType <- require v "Expected SmellType"  $ getSmellType smellName
   smellLimit <- parseField "limit" v >>= parseInt
-  return [SettingSmell smellType smellLimit]
+  pure [SettingSmell smellType smellLimit]
     where
       require :: Val -> String -> Maybe a -> Parser a
-      require _ _ (Just a) = return a
+      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 (return True) parseBool
-    groupImports <- parseFieldOpt "imports" v >>= maybe (return []) (parseArray >=> mapM parseImport)
-    groupGhcImports <- parseFieldOpt "imports" v >>= maybe (return []) (parseArray >=> mapM parseImportGHC)
-    groupRules <- parseFieldOpt "rules" v >>= maybe (return []) parseArray >>= concatMapM parseRule
+    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 isBuiltin)
     allowFields v ["name","enabled","imports","rules"]
-    return Group{..}
+    pure Group{..}
     where
         parseImport v = do
             x <- parseString v
             case word1 x of
-                ("package", x) -> return $ Left x
-                _ -> Right <$> parseHSE parseImportDeclWithMode v
-        parseImportGHC v = do
-            x <- parseString v
-            case word1 x of
-                 ("package", x) -> return $ Left x
+                 ("package", x) -> pure $ Left x
                  _ -> Right . extendInstances <$> parseGHC parseImportDeclGhcWithMode v
 
 ruleToGroup :: [Either HintRule Classify] -> Group
-ruleToGroup = Group "" True [] []
+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
-        hintRuleLHS <- parseField "lhs" v >>= parseHSE parseExpWithMode
-        hintRuleRHS <- parseField "rhs" v >>= parseHSE parseExpWithMode
-        hintRuleNotes <- parseFieldOpt "note" v >>= maybe (return []) (fmap (map asNote) . parseArrayString)
-        hintRuleName <- parseFieldOpt "name" v >>= maybe (return $ guessName hintRuleLHS hintRuleRHS) parseString
-        hintRuleSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap Just . parseHSE parseExpWithMode)
-
-        hintRuleGhcLHS <- parseField "lhs" v >>= fmap extendInstances . parseGHC parseExpGhcWithMode
-        hintRuleGhcRHS <- parseField "rhs" v >>= fmap extendInstances . parseGHC parseExpGhcWithMode
-        hintRuleGhcSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap (Just . extendInstances) . parseGHC parseExpGhcWithMode)
+        hintRuleNotes <- parseFieldOpt "note" v >>= maybe (pure []) (fmap (map asNote) . parseArrayString)
+        lhs <- parseField "lhs" v >>= parseGHC parseExpGhcWithMode
+        rhs <- parseField "rhs" v >>= parseGHC parseExpGhcWithMode
+        hintRuleSide <- parseFieldOpt "side" v >>= maybe (pure Nothing) (fmap (Just . extendInstances) . parseGHC parseExpGhcWithMode)
+        hintRuleName <- parseFieldOpt "name" v >>= maybe (pure $ guessName lhs rhs) parseString
 
         allowFields v ["lhs","rhs","note","name","side"]
-        let hintRuleScope = mempty :: Scope
-        let hintRuleGhcScope = extendInstances mempty :: HsExtendInstances Scope'
-        return [Left HintRule{hintRuleSeverity=severity, ..}]
+        let hintRuleScope = mempty
+        pure $
+          Left HintRule {hintRuleSeverity = severity, hintRuleLHS = extendInstances lhs, hintRuleRHS = extendInstances rhs, ..}
+            : [Right $ Classify severity hintRuleName "" "" | not isBuiltin]
      else do
-        names <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString
-        within <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin)
-        return [Right $ Classify severity n a b | (a,b) <- within, n <- ["" | null names] ++ names]
+        names <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString
+        within <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin)
+        pure [Right $ Classify severity n a b | (a,b) <- within, n <- ["" | null names] ++ names]
 
 parseRestrict :: RestrictType -> Val -> Parser Restrict
 parseRestrict restrictType v = do
@@ -274,42 +343,75 @@
         Just def -> do
             b <- parseBool def
             allowFields v ["default"]
-            return $ Restrict restrictType b [] [] [] [] Nothing
+            pure $ Restrict restrictType b [] mempty mempty mempty mempty [] NoRestrictIdents Nothing
         Nothing -> do
-            restrictName <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString
-            restrictWithin <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin)
-            restrictAs <- parseFieldOpt "as" v >>= maybe (return []) parseArrayString
-            restrictBadIdents <- parseFieldOpt "badidents" v >>= maybe (pure []) parseArrayString
+            restrictName <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString
+            restrictWithin <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin)
+            restrictAs <- parseFieldOpt "as" 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"]
-            return Restrict{restrictDefault=True,..}
+            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)
 parseWithin v = do
-    x <- parseHSE parseExpWithMode v
-    case x of
-        Var _ (UnQual _ name) -> return [("",fromNamed name)]
-        Var _ (Qual _ (ModuleName _ mod) name) -> return [(mod, fromNamed name)]
-        Con _ (UnQual _ name) -> return [(fromNamed name,""),("",fromNamed name)]
-        Con _ (Qual _ (ModuleName _ mod) name) -> return [(mod ++ "." ++ fromNamed name,""),(mod,fromNamed name)]
-        _ -> parseFail v "Bad classification rule"
+    s <- parseString v
+    if '*' `elem` s
+        then pure [(s, "")]
+        else do
+            x <- parseGHC parseExpGhcWithMode v
+            case x of
+                L _ (HsVar _ (L _ (Unqual x))) -> pure $ f "" (occNameString x)
+                L _ (HsVar _ (L _ (Qual mod x))) -> pure $ f (moduleNameString mod) (occNameString x)
+                _ -> parseFail v "Bad classification rule"
+            where
+                f mod name@(c:_) | isUpper c = [(mod,name),(mod ++ ['.' | mod /= ""] ++ name, "")]
+                f mod name = [(mod, name)]
 
 parseSeverityKey :: Val -> Parser (Severity, Val)
 parseSeverityKey v = do
     (s, v) <- parseObject1 v
     case getSeverity s of
-        Just sev -> return (sev, v)
+        Just sev -> pure (sev, v)
         _ -> parseFail v $ "Key should be a severity (e.g. warn/error/suggest) but got " ++ s
 
 
-guessName :: Exp_ -> Exp_ -> String
+guessName :: LHsExpr GhcPs -> LHsExpr GhcPs -> String
 guessName lhs rhs
     | n:_ <- rs \\ ls = "Use " ++ n
     | n:_ <- ls \\ rs = "Redundant " ++ n
     | otherwise = defaultHintName
     where
         (ls, rs) = both f (lhs, rhs)
-        f = filter (not . isUnifyVar) . map (\x -> fromNamed (x :: Name S)) . childrenS
+        f :: LHsExpr GhcPs -> [String]
+        f x = [y | L _ (HsVar _ (L _ x)) <- universe x, let y = occNameStr x, not $ isUnifyVar y, y /= "."]
 
 
 asNote :: String -> Note
@@ -330,26 +432,17 @@
         packages = [x | ConfigPackage x <- configs]
         groups = [x | ConfigGroup x <- configs]
         settings = concat [x | ConfigSetting x <- configs]
-        packageMap = Map.fromListWith (++) [(packageName, packageModules) | Package{..} <- packages]
-        packageMap' = Map.fromListWith (++) [(packageName, fmap unextendInstances packageGhcModules) | Package{..} <- packages]
+        packageMap' = Map.fromListWith (++) [(packageName, fmap unextendInstances packageModules) | Package{..} <- packages]
         groupMap = Map.fromListWith (\new old -> new) [(groupName, groupEnabled) | Group{..} <- groups]
 
         f Group{..}
             | Map.lookup groupName groupMap == Just False = []
-            | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope,hintRuleGhcScope=scope'}) SettingClassify) groupRules
+            | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope'}) SettingClassify) groupRules
             where
-              scope = asScope packageMap groupImports
-              scope'= asScope' packageMap' (map (fmap unextendInstances) groupGhcImports)
-
-asScope :: Map.HashMap String [ImportDecl S] -> [Either String (ImportDecl S)] -> Scope
-asScope packages xs = scopeCreate $ Module an Nothing [] (concatMap f xs) []
-    where
-        f (Right x) = [x]
-        f (Left x) | Just pkg <- Map.lookup x packages = pkg
-                   | otherwise = error $ "asScope failed to do lookup, " ++ x
+              scope'= asScope' packageMap' (map (fmap unextendInstances) groupImports)
 
-asScope' :: Map.HashMap String [HsSyn.LImportDecl HsSyn.GhcPs] -> [Either String (HsSyn.LImportDecl HsSyn.GhcPs)] -> HsExtendInstances Scope'
-asScope' packages xs = HsExtendInstances $ scopeCreate' (HsSyn.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
new file mode 100644
--- /dev/null
+++ b/src/Extension.hs
@@ -0,0 +1,68 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+module Extension(
+  defaultExtensions,
+  configExtensions,
+  extensionImpliedEnabledBy,
+  extensionImplies
+  ) where
+
+import Data.List.Extra
+import Data.Map qualified as Map
+import GHC.LanguageExtensions.Type
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session qualified as GhclibParserEx
+
+badExtensions =
+  reallyBadExtensions ++
+  [ Arrows -- steals proc
+  , 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 =
+  [ TransformListComp -- steals the group keyword
+  , StaticPointers -- steals the static keyword
+  {- , XmlSyntax , RegularPatterns -} -- steals a-b and < operators
+  , AlternativeLayoutRule -- Does not play well with 'MultiWayIf'
+  , 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
+-- many files as we can.
+defaultExtensions :: [Extension]
+defaultExtensions = enumerate \\ badExtensions
+
+-- | Extensions we turn on when reading config files, don't have to deal with the whole world
+--   of variations - in particular, we might require spaces in some places.
+configExtensions :: [Extension]
+configExtensions = enumerate \\ reallyBadExtensions
+
+-- | This extension implies the following extensions are
+-- enabled/disabled.
+extensionImplies :: Extension -> ([Extension], [Extension])
+extensionImplies = \x ->Map.findWithDefault ([], []) x mp
+  where mp = Map.fromList extensionImplications
+
+-- 'x' is implied enabled by the result extensions.
+extensionImpliedEnabledBy :: Extension -> [Extension]
+extensionImpliedEnabledBy = \x -> Map.findWithDefault [] x mp
+  where
+    mp = Map.fromListWith (++) [(b, [a]) | (a, (bs, _)) <- extensionImplications, b <- bs]
+
+-- 'x' is implied disabled by the result extensions. Not called at this time.
+_extensionImpliedDisabledBy :: Extension -> [Extension]
+_extensionImpliedDisabledBy = \x -> Map.findWithDefault [] x mp
+  where
+    mp = Map.fromListWith (++) [(b, [a]) | (a, (_, bs)) <- extensionImplications, b <- bs]
+
+-- | (a, bs) means extension a implies all of bs. Uses GHC source at
+-- DynFlags.impliedXFlags
+extensionImplications :: [(Extension, ([Extension], [Extension]))]
+extensionImplications = GhclibParserEx.extensionImplications
diff --git a/src/Fixity.hs b/src/Fixity.hs
new file mode 100644
--- /dev/null
+++ b/src/Fixity.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE ViewPatterns #-}
+
+module Fixity(
+    FixityInfo, Associativity(..),
+    defaultFixities,
+    fromFixitySig, toFixitySig, toFixity,
+    ) where
+
+import GHC.Generics(Associativity(..))
+import GHC.Hs.Binds
+import GHC.Hs.Extension
+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
+
+-- Lots of things define a fixity. None define it quite right, so let's have our own type.
+
+-- | A Fixity definition, comprising the name the fixity applies to,
+--   the direction and the precedence. As an example, a source file containing:
+--
+-- > infixr 3 `foo`
+--
+--   would create @(\"foo\", RightAssociative, 3)@.
+type FixityInfo = (String, Associativity, Int)
+
+fromFixitySig :: FixitySig GhcPs -> [FixityInfo]
+fromFixitySig (FixitySig _ names (Fixity i dir)) =
+    [(rdrNameStr name, f dir, i) | name <- names]
+    where
+        f InfixL = LeftAssociative
+        f InfixR = RightAssociative
+        f InfixN = NotAssociative
+
+toFixity :: FixityInfo -> (String, Fixity)
+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)
+  where
+    assoc dir = case dir of
+      InfixL -> LeftAssociative
+      InfixR -> RightAssociative
+      InfixN -> NotAssociative
+
+toFixitySig :: FixityInfo -> FixitySig GhcPs
+toFixitySig (toFixity -> (name, x)) = FixitySig NoNamespaceSpecifier [noLocA $ mkRdrUnqual (mkVarOcc name)] x
+
+defaultFixities :: [FixityInfo]
+defaultFixities = map fromFixity $ customFixities ++ baseFixities ++ lensFixities ++ otherFixities
+
+-- List as provided at https://github.com/ndmitchell/hlint/issues/416.
+lensFixities :: [(String, Fixity)]
+lensFixities = concat
+    [ infixr_ 4 ["%%@~","<%@~","%%~","<+~","<*~","<-~","<//~","<^~","<^^~","<**~"]
+    , infix_ 4 ["%%@=","<%@=","%%=","<+=","<*=","<-=","<//=","<^=","<^^=","<**="]
+    , infixr_ 2 ["<<~"]
+    , infixr_ 9 ["#."]
+    , infixl_ 8 [".#"]
+    , infixr_ 8 ["^!","^@!"]
+    , infixl_ 1 ["&","<&>","??"]
+    , infixl_ 8 ["^.","^@."]
+    , infixr_ 9 ["<.>","<.",".>"]
+    , infixr_ 4 ["%@~",".~","+~","*~","-~","//~","^~","^^~","**~","&&~","<>~","||~","%~"]
+    , infix_ 4 ["%@=",".=","+=","*=","-=","//=","^=","^^=","**=","&&=","<>=","||=","%="]
+    , infixr_ 2 ["<~"]
+    , infixr_ 2 ["`zoom`","`magnify`"]
+    , infixl_ 8 ["^..","^?","^?!","^@..","^@?","^@?!"]
+    , infixl_ 8 ["^#"]
+    , infixr_ 4 ["<#~","#~","#%~","<#%~","#%%~"]
+    , infix_ 4 ["<#=","#=","#%=","<#%=","#%%="]
+    , infixl_ 9 [":>"]
+    , infixr_ 4 ["</>~","<</>~","<.>~","<<.>~"]
+    , infix_ 4 ["</>=","<</>=","<.>=","<<.>="]
+    , infixr_ 4 [".|.~",".&.~","<.|.~","<.&.~"]
+    , infix_ 4 [".|.=",".&.=","<.|.=","<.&.="]
+    ]
+
+otherFixities :: [(String, Fixity)]
+otherFixities = concat
+  -- hspec
+  [ infix_ 1 ["shouldBe","shouldSatisfy","shouldStartWith","shouldEndWith","shouldContain","shouldMatchList"
+              ,"shouldReturn","shouldNotBe","shouldNotSatisfy","shouldNotContain","shouldNotReturn","shouldThrow"]
+    -- quickcheck
+  , infixr_ 0 ["==>"]
+  , infix_ 4 ["==="]
+    -- esqueleto
+  , infix_ 4 ["==."]
+    -- lattices
+  , infixr_ 5 ["\\/"] -- \/
+  , infixr_ 6 ["/\\"] -- /\
+  ]
+
+customFixities :: [(String, Fixity)]
+customFixities =
+  infixl_ 1 ["`on`"]
+        -- See https://github.com/ndmitchell/hlint/issues/425
+        -- otherwise GTK apps using `on` at a different fixity have
+        -- spurious warnings.
diff --git a/src/GHC/All.hs b/src/GHC/All.hs
new file mode 100644
--- /dev/null
+++ b/src/GHC/All.hs
@@ -0,0 +1,238 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module GHC.All(
+    CppFlags(..), ParseFlags(..), defaultParseFlags,
+    parseFlagsAddFixities, parseFlagsSetLanguage,
+    ParseError(..), ModuleEx(..),
+    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 System.IO.Extra
+import Fixity
+import Extension
+import GHC.Data.FastString
+
+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 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
+    = CppSimple -- ^ Lines prefixed with @#@ are stripped.
+    | Cpphs CpphsOptions -- ^ The @cpphs@ library is used.
+
+-- | Created with 'defaultParseFlags', used by 'parseModuleEx'.
+data ParseFlags = ParseFlags
+    {cppFlags :: CppFlags -- ^ How the file is preprocessed (defaults to 'NoCpp').
+    ,baseLanguage :: Maybe Language -- ^ Base language (e.g. Haskell98, Haskell2010), defaults to 'Nothing'.
+    ,enabledExtensions :: [Extension] -- ^ List of extensions enabled for parsing, defaults to many non-conflicting extensions.
+    ,disabledExtensions :: [Extension] -- ^ List of extensions disabled for parsing, usually empty.
+    ,fixities :: [FixityInfo] -- ^ List of fixities to be aware of, defaults to those defined in @base@.
+    }
+
+-- | Default value for 'ParseFlags'.
+defaultParseFlags :: ParseFlags
+defaultParseFlags = ParseFlags CppSimple Nothing defaultExtensions [] defaultFixities
+
+-- | Given some fixities, add them to the existing fixities in 'ParseFlags'.
+parseFlagsAddFixities :: [FixityInfo] -> ParseFlags -> ParseFlags
+parseFlagsAddFixities fx x = x{fixities = fx ++ fixities x}
+
+parseFlagsSetLanguage :: (Maybe Language, ([Extension], [Extension])) -> ParseFlags -> ParseFlags
+parseFlagsSetLanguage (l, (es, ds)) x = x{baseLanguage = l, enabledExtensions = es, disabledExtensions = ds}
+
+
+runCpp :: CppFlags -> FilePath -> String -> IO String
+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
+        -- LINE pragmas always inserted when locations=True
+        dropLine (line1 -> (a,b)) | "{-# LINE " `isPrefixOf` a = b
+        dropLine x = x
+
+---------------------------------------------------------------------
+-- PARSING
+
+-- | A parse error.
+data ParseError = ParseError
+    { parseErrorLocation :: SrcSpan -- ^ Location of the error.
+    , parseErrorMessage :: String  -- ^ Message about the cause of the error.
+    , parseErrorContents :: String -- ^ Snippet of several lines (typically 5) including a @>@ character pointing at the faulty line.
+    }
+
+-- | Result of 'parseModuleEx', representing a parsed module.
+newtype ModuleEx = ModuleEx {
+    ghcModule :: Located (HsModule GhcPs)
+}
+
+-- | 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, SDoc)
+                       -> IO (Either ParseError ModuleEx)
+ghcFailOpParseModuleEx ppstr file str (loc, err) = do
+   let pe = case loc of
+            RealSrcSpan r _ -> context (srcSpanStartLine r) ppstr
+            _ -> ""
+       msg = GHC.Driver.Ppr.showSDoc baseDynFlags err
+   pure $ Left $ ParseError loc msg pe
+
+-- GHC extensions to enable/disable given HSE parse flags.
+ghcExtensionsFromParseFlags :: ParseFlags -> ([Extension], [Extension])
+ghcExtensionsFromParseFlags ParseFlags{enabledExtensions=es, disabledExtensions=ds}= (es, ds)
+
+-- GHC fixities given HSE parse flags.
+ghcFixitiesFromParseFlags :: ParseFlags -> [(String, GHC.Types.Fixity.Fixity)]
+ghcFixitiesFromParseFlags = map toFixity . fixities
+
+-- 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
+  where
+    (enable, disable) = ghcExtensionsFromParseFlags parseMode
+
+parseExpGhcWithMode :: ParseFlags -> String -> ParseResult (LHsExpr GhcPs)
+parseExpGhcWithMode parseMode s =
+  let fixities = ghcFixitiesFromParseFlags parseMode
+  in case parseExpression s $ parseModeToFlags parseMode of
+    POk pst a -> POk pst $ applyFixities fixities a
+    f@PFailed{} -> f
+
+parseImportDeclGhcWithMode :: ParseFlags -> String -> ParseResult (LImportDecl GhcPs)
+parseImportDeclGhcWithMode parseMode s =
+  parseImport s $ parseModeToFlags parseMode
+
+parseDeclGhcWithMode :: ParseFlags -> String -> ParseResult (LHsDecl GhcPs)
+parseDeclGhcWithMode parseMode s =
+  let fixities = ghcFixitiesFromParseFlags parseMode
+  in case parseDeclaration s $ parseModeToFlags parseMode of
+    POk pst a -> POk pst $ applyFixities fixities a
+    f@PFailed{} -> f
+
+-- | 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
+-- 'defaultParseFlags'), the filename, and optionally the contents of
+-- that file.
+--
+-- Note that certain programs, e.g. @main = do@ successfully parse
+-- with GHC, but then fail with an error in the renamer. These
+-- programs will return a successful parse.
+parseModuleEx :: ParseFlags -> FilePath -> Maybe String -> IO (Either ParseError ModuleEx)
+parseModuleEx flags file str = timedIO "Parse" file $ runExceptT $ do
+  str <- case str of
+    Just x -> pure x
+    Nothing | file == "-" -> liftIO getContentsUTF8
+            | otherwise -> liftIO $ readFileUTF8' file
+  str <- pure $ dropPrefix "\65279" str -- Remove the BOM if it exists, see #130.
+  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
+  -- Avoid running cpp unless CPP is enabled, see #1075.
+  str <- if not (xopt Cpp dynFlags) then pure str else liftIO $ runCpp (cppFlags flags) file str
+  -- If we preprocessed the file, re-read the pragmas.
+  dynFlags <- if not (xopt Cpp dynFlags) then pure dynFlags
+              else withExceptT (parsePragmasErr str) $ ExceptT (parsePragmasIntoDynFlags baseDynFlags enableDisableExts file str)
+  dynFlags <- pure $ lang_set dynFlags $ baseLanguage flags
+  -- Done with pragmas. Proceed to parsing.
+  case fileToModule file str dynFlags of
+    POk s a -> do
+      let errs = bagToList . getMessages $ GhcPsMessage <$> snd (getPsMessages s)
+      if not $ null errs then
+        ExceptT $ parseFailureErr dynFlags str file str $ NE.fromList errs
+      else do
+        let fixes = fixitiesFromModule a ++ ghcFixitiesFromParseFlags flags
+        pure $ ModuleEx (applyFixities fixes a)
+    PFailed s ->
+      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.
+    parsePragmasErr src msg =
+      let loc = mkSrcLoc (mkFastString file) (1 :: Int) (1 :: Int)
+      in ParseError (mkSrcSpan loc loc) msg src
+
+    parseFailureErr dynFlags ppstr file str errs =
+      let errMsg = NE.head errs
+          loc = errMsgSpan 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.
+context :: Int -> String -> String
+context lineNo src =
+    unlines $ dropWhileEnd (all isSpace) $ dropWhile (all isSpace) $
+    zipWith (++) ticks $ take 5 $ drop (lineNo - 3) $ lines src ++ ["","","","",""]
+    where ticks = drop (3 - lineNo) ["  ","  ","> ","  ","  "]
diff --git a/src/GHC/Util.hs b/src/GHC/Util.hs
--- a/src/GHC/Util.hs
+++ b/src/GHC/Util.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE ViewPatterns #-}
 
 module GHC.Util (
     module GHC.Util.View
@@ -5,55 +7,107 @@
   , module GHC.Util.ApiAnnotation
   , module GHC.Util.HsDecl
   , module GHC.Util.HsExpr
-  , module GHC.Util.HsType
-  , module GHC.Util.LanguageExtensions.Type
-  , module GHC.Util.Pat
-  , module GHC.Util.Module
-  , module GHC.Util.Outputable
   , module GHC.Util.SrcLoc
   , module GHC.Util.DynFlags
   , module GHC.Util.Scope
-  , module GHC.Util.RdrName
   , module GHC.Util.Unify
-
   , parsePragmasIntoDynFlags
-  , parseFileGhcLib, parseExpGhcLib, parseImportGhcLib
+  , fileToModule
+  , pattern SrcSpan, srcSpanFilename, srcSpanStartLine', srcSpanStartColumn, srcSpanEndLine', srcSpanEndColumn
+  , pattern SrcLoc, srcFilename, srcLine, srcColumn
+  , showSrcSpan,
   ) where
 
 import GHC.Util.View
 import GHC.Util.FreeVars
 import GHC.Util.ApiAnnotation
 import GHC.Util.HsExpr
-import GHC.Util.HsType
 import GHC.Util.HsDecl
-import GHC.Util.LanguageExtensions.Type
-import GHC.Util.Pat
-import GHC.Util.Module
-import GHC.Util.Outputable
 import GHC.Util.SrcLoc
 import GHC.Util.DynFlags
-import GHC.Util.RdrName
 import GHC.Util.Scope
 import GHC.Util.Unify
 
-import qualified Language.Haskell.GhclibParserEx.Parse as GhclibParserEx
-import Language.Haskell.GhclibParserEx.DynFlags (parsePragmasIntoDynFlags)
+import Language.Haskell.GhclibParserEx.GHC.Parser (parseFile)
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session (parsePragmasIntoDynFlags)
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
-import HsSyn
-import Lexer
-import SrcLoc
-import DynFlags
+import GHC.Hs
+import GHC.Parser.Lexer
+import GHC.Types.SrcLoc
+import GHC.Driver.Session
+import GHC.Data.FastString
 
 import System.FilePath
 import Language.Preprocessor.Unlit
 
-parseExpGhcLib :: String -> DynFlags -> ParseResult (LHsExpr GhcPs)
-parseExpGhcLib = GhclibParserEx.parseExpression
+fileToModule :: FilePath -> String -> DynFlags -> ParseResult (Located (HsModule GhcPs))
+fileToModule filename str flags =
+  parseFile filename flags
+    (if takeExtension filename /= ".lhs" then str else unlit filename str)
 
-parseImportGhcLib :: String -> DynFlags -> ParseResult (LImportDecl GhcPs)
-parseImportGhcLib = GhclibParserEx.parseImport
+{-# COMPLETE SrcSpan #-}
+-- | The \"Line'\" thing is because there is already e.g. 'SrcLoc.srcSpanStartLine'
+pattern SrcSpan :: String -> Int -> Int -> Int -> Int -> SrcSpan
+pattern SrcSpan
+  { srcSpanFilename
+  , srcSpanStartLine'
+  , srcSpanStartColumn
+  , srcSpanEndLine'
+  , srcSpanEndColumn
+  }
+  <-
+    (toOldeSpan ->
+      ( srcSpanFilename
+      , srcSpanStartLine'
+      , srcSpanStartColumn
+      , srcSpanEndLine'
+      , srcSpanEndColumn
+      ))
 
-parseFileGhcLib :: FilePath -> String -> DynFlags -> ParseResult (Located (HsModule GhcPs))
-parseFileGhcLib filename str flags =
-  GhclibParserEx.parseFile filename flags
-    (if takeExtension filename /= ".lhs" then str else unlit filename str)
+toOldeSpan :: SrcSpan -> (String, Int, Int, Int, Int)
+toOldeSpan (RealSrcSpan span _) =
+  ( unpackFS $ srcSpanFile span
+  , srcSpanStartLine span
+  , srcSpanStartCol span
+  , srcSpanEndLine span
+  , srcSpanEndCol span
+  )
+-- 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 _) =
+  ( "no-span"
+  , -1
+  , -1
+  , -1
+  , -1
+  )
+
+{-# COMPLETE SrcLoc #-}
+pattern SrcLoc :: String -> Int -> Int -> SrcLoc
+pattern SrcLoc
+  { srcFilename
+  , srcLine
+  , srcColumn
+  }
+  <-
+    (toOldeLoc ->
+      ( srcFilename
+      , srcLine
+      , srcColumn
+      ))
+
+toOldeLoc :: SrcLoc -> (String, Int, Int)
+toOldeLoc (RealSrcLoc loc _) =
+  ( unpackFS $ srcLocFile loc
+  , srcLocLine loc
+  , srcLocCol loc
+  )
+toOldeLoc (UnhelpfulLoc _) =
+  ( "no-loc"
+  , -1
+  , -1
+  )
+
+showSrcSpan :: SrcSpan -> String
+showSrcSpan = unsafePrettyPrint
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, langExts
-  , mkFlags, mkLangExts
+    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).
@@ -83,21 +95,20 @@
                              <|> stripPrefixCI "OPTIONS " s]
              , let opts = words rest]
 
--- Language extensions. The first element of the pair is the (located)
--- annotation comment that enables the extensions enumerated by he
--- second element of the pair.
-langExts :: [(Located AnnotationComment, String)]
-         -> [(Located AnnotationComment, [String])]
-langExts ps =
+-- Language pragmas. The first element of the
+-- pair is the (located) annotation comment that enables the
+-- 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 =
-  LL loc $ AnnBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}")
+mkFlags :: NoCommentsLocation -> [String] -> LEpaComment
+mkFlags anc flags =
+  L anc $ EpaComment (EpaBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}")) (epaLocationRealSrcSpan anc)
 
-mkLangExts :: SrcSpan -> [String] -> Located AnnotationComment
-mkLangExts loc exts =
-  LL 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
@@ -1,38 +1,43 @@
 {-# LANGUAGE MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-}
-module GHC.Util.Brackets (Brackets'(..), isApp,isOpApp,isAnyApp) where
+{-# OPTIONS_GHC -Wno-incomplete-patterns -Wno-overlapping-patterns #-}
 
-import HsSyn
-import SrcLoc
-import BasicTypes
+module GHC.Util.Brackets (Brackets(..), isApp,isOpApp,isAnyApp) where
+
+import GHC.Hs
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
+import Refact.Types
 
-class Brackets' a where
-  remParen' :: a -> Maybe a -- Remove one paren or nothing if there is no paren.
-  addParen' :: a -> a -- Write out a paren.
+class Brackets a where
+  remParen :: a -> Maybe a -- Remove one paren or nothing if there is no paren.
+  addParen :: a -> a -- Write out a paren.
   -- | Is this item lexically requiring no bracketing ever i.e. is
   -- totally atomic.
-  isAtom' :: a -> Bool
+  isAtom :: a -> Bool
   -- | Is the child safe free from brackets in the parent
   -- position. Err on the side of caution, True = don't know.
-  needBracket' :: Int -> a -> a -> Bool
+  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,
-  -- when stripping brackets (c.f. 'Hint.Brackets'), don't remove the
+  -- when stripping brackets (c.f. 'Hint.Brackets), don't remove the
   -- paren's surrounding a section - they are required.
-  remParen' (LL _ (HsPar _ (LL _ SectionL{}))) = Nothing
-  remParen' (LL _ (HsPar _ (LL _ SectionR{}))) = Nothing
-  remParen' (LL _ (HsPar _ x)) = Just x
-  remParen' _ = Nothing
+  remParen (L _ (HsPar _ (L _ SectionL{}))) = Nothing
+  remParen (L _ (HsPar _ (L _ SectionR{}))) = Nothing
+  remParen (L _ (HsPar _ x)) = Just x
+  remParen _ = Nothing
 
-  addParen' e = noLoc $ HsPar noExt e
+  addParen = nlHsPar
 
-  isAtom' (LL _ x) = case x of
+  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).
@@ -43,8 +48,12 @@
       RecordCon{} -> True
       RecordUpd{} -> True
       ArithSeq{}-> True
-      HsBracket{} -> True
-      HsSpliceE {} -> 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
+      HsTypedSplice{} -> True
+      HsUntypedSplice{} -> True
       HsOverLit _ x | not $ isNegativeOverLit x -> True
       HsLit _ x     | not $ isNegativeLit x     -> True
       _  -> False
@@ -60,38 +69,57 @@
         isNegativeOverLit OverLit {ol_val=HsIntegral i} = il_neg i
         isNegativeOverLit OverLit {ol_val=HsFractional f} = fl_neg f
         isNegativeOverLit _ = False
-  isAtom' _ = False -- '{-# COMPLETE LL #-}'
+  isAtom _ = False -- '{-# COMPLETE L #-}'
 
-  needBracket' i parent child -- Note: i is the index in children, not in the AST.
-     | isAtom' child = False
-     | isSection parent, LL _ HsApp{} <- child = False
-     | LL _ OpApp{} <- parent, LL _ HsApp{} <- child = False
-     | LL _ HsLet{} <- parent, LL _ HsApp{} <- child = False
-     | LL _ HsDo{} <- parent = False
-     | LL _ ExplicitList{} <- parent = False
-     | LL _ ExplicitTuple{} <- parent = False
-     | LL _ HsIf{} <- parent, isAnyApp child = False
-     | LL _ HsApp{} <- parent, i == 0, LL _ HsApp{} <- child = False
-     | LL _ ExprWithTySig{} <- parent, i == 0, isApp child = False
-     | LL _ RecordCon{} <- parent = False
-     | LL _ RecordUpd{} <- parent, i /= 0 = False
-     | LL _ HsCase{} <- parent, i /= 0 || isAnyApp child = False
-     | LL _ HsLam{} <- parent = False -- might be either the RHS of a PViewPat, or the lambda body (neither needs brackets)
-     | LL _ HsPar{} <- parent = False
-     | LL _ HsDo {} <- parent = False
+  needBracket i parent child -- Note: i is the index in children, not in the AST.
+     | isAtom child = False
+     | isSection parent, L _ HsApp{} <- child = False
+     | L _ OpApp{} <- parent, L _ HsApp{} <- child, i /= 0 || isAtomOrApp child = False
+     | L _ ExplicitList{} <- parent = False
+     | L _ ExplicitTuple{} <- parent = False
+     | L _ HsIf{} <- parent, isAnyApp child = False
+     | L _ HsApp{} <- parent, i == 0, L _ HsApp{} <- child = False
+     | L _ ExprWithTySig{} <- parent, i == 0, isApp child = False
+     | L _ RecordCon{} <- parent = False
+     | L _ RecordUpd{} <- parent, i /= 0 = False
+
+     -- These all have view patterns embedded within them, or are naturally followed by ->, so we have to watch out for
+     -- @(x::y) -> z@ which is valid, as either a type annotation, or a view pattern.
+     | L _ HsLet{} <- parent, isApp child = False
+     | L _ HsDo{} <- parent, isAnyApp child = False
+     | L _ HsLam{} <- parent, isAnyApp child = False
+     | L _ HsCase{} <- parent, isAnyApp child = False
+
+     | L _ HsPar{} <- parent = False
      | otherwise = True
 
-instance Brackets' (Pat GhcPs) where
-  remParen' (LL _ (ParPat _ x)) = Just x
-  remParen' _ = Nothing
-  addParen' e = noLoc $ ParPat noExt e
+  findType _ = Expr
 
-  isAtom' (LL _ x) = case x of
+-- | Am I an HsApp such that having me in an infix doesn't require brackets.
+--   Before BlockArguments that was _all_ HsApps. Now, imagine:
+--
+--   (f \x -> x) *> ...
+--   (f do x) *> ...
+isAtomOrApp :: LocatedA (HsExpr GhcPs) -> Bool
+isAtomOrApp x | isAtom x = True
+isAtomOrApp (L _ (HsApp _ _ x)) = isAtomOrApp x
+isAtomOrApp _ = False
+
+instance Brackets (LocatedA (Pat GhcPs)) where
+  remParen (L _ (ParPat _ x)) = Just x
+  remParen _ = Nothing
+
+  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
@@ -108,20 +136,22 @@
       isSignedLit HsFloatPrim{} = True
       isSignedLit HsDoublePrim{} = True
       isSignedLit _ = False
-  isAtom' _ = False -- '{-# COMPLETE LL #-}'
+  isAtom _ = False -- '{-# COMPLETE L #-}'
 
-  needBracket' _ parent child
-    | isAtom' child = False
-    | LL _ TuplePat{} <- parent = False
-    | LL _ ListPat{} <- parent = False
+  needBracket _ parent child
+    | isAtom child = False
+    | L _ TuplePat{} <- parent = False
+    | L _ ListPat{} <- parent = False
     | otherwise = True
 
-instance Brackets' (LHsType GhcPs) where
-  remParen' (LL _ (HsParTy _ x)) = Just x
-  remParen' _ = Nothing
-  addParen' e = noLoc $ HsParTy noExt e
+  findType _ = Pattern
 
-  isAtom' (LL _ x) = case x of
+instance Brackets (LocatedA (HsType GhcPs)) where
+  remParen (L _ (HsParTy _ x)) = Just x
+  remParen _ = Nothing
+  addParen e = noLocA $ HsParTy noAnn e
+
+  isAtom (L _ x) = case x of
       HsParTy{} -> True
       HsTupleTy{} -> True
       HsListTy{} -> True
@@ -129,21 +159,23 @@
       HsExplicitListTy{} -> True
       HsTyVar{} -> True
       HsSumTy{} -> True
-      HsSpliceTy{} -> True
       HsWildCardTy{} -> True
+      -- HsSpliceTy{} is not atomic, because of @($foo)
       _ -> False
-  isAtom' _ = False -- '{-# COMPLETE LL #-}'
+  isAtom _ = False -- '{-# COMPLETE L #-}'
 
-  needBracket' _ parent child
-    | isAtom' child = False
+  needBracket _ parent child
+    | isAtom child = False
 -- a -> (b -> c) is not a required bracket, but useful for documentation about arity etc.
 --        | TyFun{} <- parent, i == 1, TyFun{} <- child = False
-    | LL _ HsFunTy{} <- parent, LL _ HsAppTy{} <- child = False
-    | LL _ HsTupleTy{} <- parent = False
-    | LL _ HsListTy{} <- parent = False
-    | LL _ HsExplicitTupleTy{} <- parent = False
-    | LL _ HsListTy{} <- parent = False
-    | LL _ HsExplicitListTy{} <- parent = False
-    | LL _ HsOpTy{} <- parent, LL _ HsAppTy{} <- child = False
-    | LL _ HsParTy{} <- parent = False
+    | L _ HsFunTy{} <- parent, L _ HsAppTy{} <- child = False
+    | L _ HsTupleTy{} <- parent = False
+    | L _ HsListTy{} <- parent = False
+    | L _ HsExplicitTupleTy{} <- parent = False
+    | L _ HsListTy{} <- parent = False
+    | L _ HsExplicitListTy{} <- parent = False
+    | L _ HsOpTy{} <- parent, L _ HsAppTy{} <- child = False
+    | L _ HsParTy{} <- parent = False
     | otherwise = True
+
+  findType _ = Type
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,19 +1,14 @@
-module GHC.Util.DynFlags (baseDynFlags) where
+module GHC.Util.DynFlags (initGlobalDynFlags, baseDynFlags) where
 
-import DynFlags
-import GHC.LanguageExtensions.Type
-import Data.List.Extra
-import Language.Haskell.GhclibParserEx.Config
+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,28 +1,25 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ViewPatterns #-}
 
 module GHC.Util.FreeVars (
-    vars', varss', pvars', freeVarSet'
-  , Vars' (..), FreeVars'(..) , AllVars' (..)
+    vars, varss, pvars,
+    Vars (..), FreeVars(..) , AllVars (..)
   ) where
 
-import RdrName
-import HsTypes
-import OccName
-import Name
-import HsSyn
-import SrcLoc
-import Bag (bagToList)
+import GHC.Types.Name.Reader
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name
+import GHC.Hs
+import GHC.Types.SrcLoc
 
-import Data.Generics.Uniplate.Data ()
-import Data.Generics.Uniplate.Operations
+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
@@ -31,238 +28,233 @@
 ( ^- ) = Set.difference
 
 -- See [Note : Space leaks lurking here?] below.
-data Vars' = Vars'{bound' :: Set OccName, free' :: Set OccName}
+data Vars = Vars{bound :: Set OccName, free :: Set OccName}
 
 -- Useful for debugging.
-instance Show Vars' where
-  show (Vars' bs fs) = "bound : " ++
+instance Show Vars where
+  show (Vars bs fs) = "bound : " ++
     show (map occNameString (Set.toList bs)) ++
     ", free : " ++ show (map occNameString (Set.toList fs))
 
-instance Semigroup Vars' where
-    Vars' x1 x2 <> Vars' y1 y2 = Vars' (x1 ^+ y1) (x2 ^+ y2)
+instance Semigroup Vars where
+    Vars x1 x2 <> Vars y1 y2 = Vars (x1 ^+ y1) (x2 ^+ y2)
 
-instance Monoid Vars' where
-    mempty = Vars' Set.empty Set.empty
-    mconcat vs = Vars' (Set.unions $ map bound' vs) (Set.unions $ map free' vs)
+instance Monoid Vars where
+    mempty = Vars Set.empty Set.empty
+    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
+-- A type `a` is a model of `AllVars a` if exists a function
+-- `allVars` for producing a pair of the bound and free variable
 -- sets in a value of `a`.
-class AllVars' a where
+class AllVars a where
     -- | Return the variables, erring on the side of more free
     -- variables.
-    allVars' :: a -> Vars'
+    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
+-- A type `a` is a model of `FreeVars a` if exists a function
+-- `freeVars` for producing a set of free variables of a value of
 -- `a`.
-class FreeVars' a where
+class FreeVars a where
     -- | Return the variables, erring on the side of more free
     -- variables.
-    freeVars' :: a -> Set OccName
+    freeVars :: a -> Set OccName
 
 -- Trivial instances.
-instance AllVars' Vars'  where allVars' = id
-instance FreeVars' (Set OccName) where freeVars' = id
+instance AllVars Vars  where allVars = id
+instance FreeVars (Set OccName) where freeVars = id
 -- [Note : Space leaks lurking here?]
 -- ==================================
 -- We make use of `foldr`. @cocreature suggests we want bangs on `data
--- Vars` and replace usages of `mconcat` with `foldl'`.
-instance (AllVars' a) => AllVars' [a] where  allVars' = mconcat . map allVars'
-instance (FreeVars' a) => FreeVars' [a] where  freeVars' = Set.unions . map freeVars'
+-- Vars` and replace usages of `mconcat` with `foldl`.
+instance (AllVars a) => AllVars [a] where  allVars = mconcatMap allVars
+instance (FreeVars a) => FreeVars [a] where  freeVars = Set.unions . map freeVars
 
 -- Construct a `Vars` value with no bound vars.
-freeVars_' :: (FreeVars' a) => a -> Vars'
-freeVars_' = Vars' Set.empty . freeVars'
+freeVars_ :: (FreeVars a) => a -> Vars
+freeVars_ = Vars Set.empty . freeVars
 
--- `inFree' a b` is the set of free variables in 'a' together with the
--- free variables in 'b' not bound in 'a'.
-inFree' :: (AllVars' a, FreeVars' b) => a -> b -> Set OccName
-inFree' a b = free' aa ^+ (freeVars' b ^- bound' aa)
-    where aa = allVars' a
+-- `inFree a b` is the set of free variables in a together with the
+-- free variables in b not bound in a.
+inFree :: (AllVars a, FreeVars b) => a -> b -> Set OccName
+inFree a b = free aa ^+ (freeVars b ^- bound aa)
+    where aa = allVars a
 
--- `inVars' a b` is a value of `Vars_'` with bound variables the union
--- of the bound variables of 'a' and 'b' and free variables the union
--- of the free variables of 'a' and the free variables of 'b' not
--- bound by 'a'.
-inVars' :: (AllVars' a, AllVars' b) => a -> b -> Vars'
-inVars' a b =
-  Vars' (bound' aa ^+ bound' bb) (free' aa ^+ (free' bb ^- bound' aa))
-    where aa = allVars' a
-          bb = allVars' b
+-- `inVars a b` is a value of `Vars_` with bound variables the union
+-- of the bound variables of a and b and free variables the union
+-- of the free variables of a and the free variables of b not
+-- bound by a.
+inVars :: (AllVars a, AllVars b) => a -> b -> Vars
+inVars a b =
+  Vars (bound aa ^+ bound bb) (free aa ^+ (free bb ^- bound aa))
+    where aa = allVars a
+          bb = allVars b
 
 -- Get an `OccName` out of a reader name.
-unqualNames' :: Located RdrName -> [OccName]
-unqualNames' (dL -> L _ (Unqual x)) = [x]
-unqualNames' (dL -> L _ (Exact x)) = [nameOccName x]
-unqualNames' _ = []
-
-instance FreeVars' (LHsExpr GhcPs) where
-  freeVars' (dL -> L _ (HsVar _ x)) = Set.fromList $ unqualNames' x -- Variable.
-  freeVars' (dL -> L _ (HsUnboundVar _ x)) = Set.fromList [unboundVarOcc x] -- Unbound variable; also used for "holes".
-  freeVars' (dL -> L _ (HsLam _ mg)) = free' (allVars' mg) -- Lambda abstraction. Currently always a single match.
-  freeVars' (dL -> L _ (HsLamCase _ mg)) = free' (allVars' mg) -- Lambda-case.
-  freeVars' (dL -> L _ (HsCase _ of_ MG{mg_alts=(dL -> L _ ms)})) = freeVars' of_ ^+ free' (allVars' ms) -- Case expr.
-  freeVars' (dL -> L _ (HsLet _ binds e)) = inFree' binds e -- Let (rec).
-  freeVars' (dL -> L _ (HsDo _ ctxt (dL -> L _ stmts))) = free' (allVars' stmts) -- Do block.
-  freeVars' (dL -> L _ (RecordCon _ _ (HsRecFields flds _))) = Set.unions $ map freeVars' flds -- Record construction.
-  freeVars' (dL -> L _ (RecordUpd _ e flds)) = Set.unions $ freeVars' e : map freeVars' flds -- Record update.
-  freeVars' (dL -> L _ (HsMultiIf _ grhss)) = free' (allVars' grhss) -- Multi-way if.
-
-  freeVars' (dL -> L _ HsConLikeOut{}) = mempty -- After typechecker.
-  freeVars' (dL -> L _ HsRecFld{}) = mempty -- Variable pointing to a record selector.
-  freeVars' (dL -> L _ HsOverLabel{}) = mempty -- Overloaded label. The id of the in-scope 'fromLabel'.
-  freeVars' (dL -> L _ HsIPVar{}) = mempty -- Implicit parameter.
-  freeVars' (dL -> L _ HsOverLit{}) = mempty -- Overloaded literal.
-  freeVars' (dL -> L _ HsLit{}) = mempty -- Simple literal.
-  freeVars' (dL -> L _ HsRnBracketOut{}) = mempty -- Renamer produces these.
-  freeVars' (dL -> L _ HsTcBracketOut{}) = mempty -- Typechecker produces these.
-  freeVars' (dL -> L _ HsWrap{}) = mempty -- Typechecker output.
-
-  -- freeVars' (dL -> e@(L _ HsAppType{})) = freeVars' $ children e -- Visible type application e.g. 'f @ Int x y'.
-  -- freeVars' (dL -> e@(L _ HsApp{})) = freeVars' $ children e -- Application.
-  -- freeVars' (dL -> e@(L _ OpApp{})) = freeVars' $ children e -- Operator application.
-  -- freeVars' (dL -> e@(L _ NegApp{})) = freeVars' $ children e -- Negation operator.
-  -- freeVars' (dL -> e@(L _ HsPar{})) = freeVars' $ children e -- Parenthesized expr.
-  -- freeVars' (dL -> e@(L _ SectionL{})) = freeVars' $ children e -- Left section.
-  -- freeVars' (dL -> e@(L _ SectionR{})) = freeVars' $ children e -- Right section.
-  -- freeVars' (dL -> e@(L _ ExplicitTuple{})) = freeVars' $ children e -- Explicit tuple and sections thereof.
-  -- freeVars' (dL -> e@(L _ ExplicitSum{})) = freeVars' $ children e -- Used for unboxed sum types.
-  -- freeVars' (dL -> e@(L _ HsIf{})) = freeVars' $ children e -- If.
-  -- freeVars' (dL -> e@(L _ ExplicitList{})) = freeVars' $ children e -- Syntactic list e.g. '[a, b, c]'.
-  -- freeVars' (dL -> e@(L _ ExprWithTySig{})) = freeVars' $ children e -- Expr with type signature.
-  -- freeVars' (dL -> e@(L _ ArithSeq {})) = freeVars' $ children e -- Arithmetic sequence.
-  -- freeVars' (dL -> e@(L _ HsSCC{})) = freeVars' $ children e -- Set cost center pragma (expr whose const is to be measured).
-  -- freeVars' (dL -> e@(L _ HsCoreAnn{})) = freeVars' $ children e -- Pragma.
-  -- freeVars' (dL -> e@(L _ HsBracket{})) = freeVars' $ children e -- Haskell bracket.
-  -- freeVars' (dL -> e@(L _ HsSpliceE{})) = freeVars' $ children e -- Template haskell splice expr.
-  -- freeVars' (dL -> e@(L _ HsProc{})) = freeVars' $ children e -- Proc notation for arrows.
-  -- freeVars' (dL -> e@(L _ HsStatic{})) = freeVars' $ children e -- Static pointers extension.
-  -- freeVars' (dL -> e@(L _ HsArrApp{})) = freeVars' $ children e -- Arrow tail or arrow application.
-  -- freeVars' (dL -> e@(L _ HsArrForm{})) = freeVars' $ children e -- Come back to it. Arrow tail or arrow application.
-  -- freeVars' (dL -> e@(L _ HsTick{})) = freeVars' $ children e -- Haskell program coverage (Hpc) support.
-  -- freeVars' (dL -> e@(L _ HsBinTick{})) = freeVars' $ children e -- Haskell program coverage (Hpc) support.
-  -- freeVars' (dL -> e@(L _ HsTickPragma{})) = freeVars' $ children e -- Haskell program coverage (Hpc) support.
-  -- freeVars' (dL -> e@(L _ EAsPat{})) = freeVars' $ children e -- Expr as pat.
-  -- freeVars' (dL -> e@(L _ EViewPat{})) = freeVars' $ children e -- View pattern.
-  -- freeVars' (dL -> e@(L _ ELazyPat{})) = freeVars' $ children e -- Lazy pattern.
-
-  freeVars' e = freeVars' $ children e
-
-instance FreeVars' (LHsRecField GhcPs (LHsExpr GhcPs)) where
-   freeVars' (dL -> L _ (HsRecField _ x _)) = freeVars' x
-
-instance FreeVars' (LHsRecUpdField GhcPs) where
-  freeVars' (dL -> L _ (HsRecField _ x _)) = freeVars' x
+unqualNames :: LocatedN RdrName -> [OccName]
+unqualNames (L _ (Unqual x)) = [x]
+unqualNames (L _ (Exact x)) = [nameOccName x]
+unqualNames _ = []
 
-instance AllVars' (LPat GhcPs) where
-  allVars' (VarPat _ (dL -> L _ x)) = Vars' (Set.singleton $ rdrNameOcc x) Set.empty -- Variable pattern.
-  allVars' (AsPat _  n x) = allVars' (VarPat noExt n :: Pat GhcPs) <> allVars' x -- As pattern.
-  allVars' (ConPatIn _ (RecCon (HsRecFields flds _))) = allVars' flds
-  allVars' (NPlusKPat _ n _ _ _ _) = allVars' (VarPat noExt n :: Pat GhcPs) -- n+k pattern.
-  allVars' (ViewPat _ e p) = freeVars_' e <> allVars' p -- View pattern.
+instance FreeVars (LocatedA (HsExpr GhcPs)) where
+  freeVars (L _ (HsVar _ x)) = Set.fromList $ unqualNames x -- Variable.
+  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))) = 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)]
 
-  allVars' WildPat{} = mempty -- Wildcard pattern.
-  allVars' ConPatOut{} = mempty -- Renamer/typechecker.
-  allVars' LitPat{} = mempty -- Literal pattern.
-  allVars' NPat{} = mempty -- Natural pattern.
+  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.
 
-  -- allVars' p@SplicePat{} = allVars' $ children p -- Splice pattern (includes quasi-quotes).
-  -- allVars' p@SigPat{} = allVars' $ children p -- Pattern with a type signature.
-  -- allVars' p@CoPat{} = allVars' $ children p -- Coercion pattern.
-  -- allVars' p@LazyPat{} = allVars' $ children p -- Lazy pattern.
-  -- allVars' p@ParPat{} = allVars' $ children p -- Parenthesized pattern.
-  -- allVars' p@BangPat{} = allVars' $ children p -- Bang pattern.
-  -- allVars' p@ListPat{} = allVars' $ children p -- Syntactic list.
-  -- allVars' p@TuplePat{} = allVars' $ children p -- Tuple sub patterns.
-  -- allVars' p@SumPat{} = allVars' $ children p -- Anonymous sum pattern.
+  -- freeVars (e@(L _ HsAppType{})) = freeVars $ children e -- Visible type application e.g. f @ Int x y.
+  -- freeVars (e@(L _ HsApp{})) = freeVars $ children e -- Application.
+  -- freeVars (e@(L _ OpApp{})) = freeVars $ children e -- Operator application.
+  -- freeVars (e@(L _ NegApp{})) = freeVars $ children e -- Negation operator.
+  -- freeVars (e@(L _ HsPar{})) = freeVars $ children e -- Parenthesized expr.
+  -- freeVars (e@(L _ SectionL{})) = freeVars $ children e -- Left section.
+  -- freeVars (e@(L _ SectionR{})) = freeVars $ children e -- Right section.
+  -- freeVars (e@(L _ ExplicitTuple{})) = freeVars $ children e -- Explicit tuple and sections thereof.
+  -- freeVars (e@(L _ ExplicitSum{})) = freeVars $ children e -- Used for unboxed sum types.
+  -- freeVars (e@(L _ HsIf{})) = freeVars $ children e -- If.
+  -- freeVars (e@(L _ ExplicitList{})) = freeVars $ children e -- Syntactic list e.g. [a, b, c].
+  -- freeVars (e@(L _ ExprWithTySig{})) = freeVars $ children e -- Expr with type signature.
+  -- freeVars (e@(L _ ArithSeq {})) = freeVars $ children e -- Arithmetic sequence.
+  -- freeVars (e@(L _ HsSCC{})) = freeVars $ children e -- Set cost center pragma (expr whose const is to be measured).
+  -- freeVars (e@(L _ HsCoreAnn{})) = freeVars $ children e -- Pragma.
+  -- freeVars (e@(L _ HsBracket{})) = freeVars $ children e -- Haskell bracket.
+  -- freeVars (e@(L _ HsSpliceE{})) = freeVars $ children e -- Template haskell splice expr.
+  -- freeVars (e@(L _ HsProc{})) = freeVars $ children e -- Proc notation for arrows.
+  -- freeVars (e@(L _ HsStatic{})) = freeVars $ children e -- Static pointers extension.
+  -- freeVars (e@(L _ HsArrApp{})) = freeVars $ children e -- Arrow tail or arrow application.
+  -- freeVars (e@(L _ HsArrForm{})) = freeVars $ children e -- Come back to it. Arrow tail or arrow application.
+  -- freeVars (e@(L _ HsTick{})) = freeVars $ children e -- Haskell program coverage (Hpc) support.
+  -- freeVars (e@(L _ HsBinTick{})) = freeVars $ children e -- Haskell program coverage (Hpc) support.
+  -- freeVars (e@(L _ HsTickPragma{})) = freeVars $ children e -- Haskell program coverage (Hpc) support.
+  -- freeVars (e@(L _ EAsPat{})) = freeVars $ children e -- Expr as pat.
+  -- freeVars (e@(L _ EViewPat{})) = freeVars $ children e -- View pattern.
+  -- freeVars (e@(L _ ELazyPat{})) = freeVars $ children e -- Lazy pattern.
 
-  allVars' p = allVars' $ children p
+  freeVars e = freeVars $ children e
 
-instance AllVars' (LHsRecField GhcPs (LPat GhcPs)) where
-   allVars' (dL -> L _ (HsRecField _ x _)) = allVars' x
+instance FreeVars (HsTupArg GhcPs) where
+  freeVars (Present _ args) = freeVars args
+  freeVars _ = mempty
 
-instance AllVars' (LStmt GhcPs (LHsExpr GhcPs)) where
-  allVars' (dL -> L _ (LastStmt _ expr _ _)) = freeVars_' expr -- The last stmt of a 'ListComp', 'MonadComp', 'DoExpr','MDoExpr'.
-  allVars' (dL -> L _ (BindStmt _ pat expr _ _)) = allVars' pat <> freeVars_' expr -- A generator e.g. 'x <- [1, 2, 3]'.
-  allVars' (dL -> L _ (BodyStmt _ expr _ _)) = freeVars_' expr -- A boolean guard e.g. 'even x'.
-  allVars' (dL -> L _ (LetStmt _ binds)) = allVars' binds -- A local declaration e.g. 'let y = x + 1'
-  allVars' (dL -> 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' (dL -> L _ (RecStmt _ stmts _ _ _ _ _)) = allVars' stmts -- A recursive binding for a group of arrows.
+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
 
-  allVars' (dL -> L _ ApplicativeStmt{}) = mempty -- Generated by the renamer.
-  allVars' (dL -> L _ ParStmt{}) = mempty -- Parallel list thing. Come back to it.
+instance FreeVars (LocatedA (HsFieldBind (LocatedAn NoEpAnns (FieldLabelStrings GhcPs)) (LocatedA (HsExpr GhcPs)))) where
+  freeVars (L _ (HsFieldBind _ _ x _)) = freeVars x
 
-  allVars' _ = mempty -- New ctor.
+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 (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 _ LitPat{}) = mempty -- Literal pattern.
+  allVars (L _ NPat{}) = mempty -- Natural pattern.
+  allVars (L _ InvisPat {}) = mempty -- since ghc-9.10.1
 
-instance AllVars' (LHsLocalBinds GhcPs) where
-  allVars' (dL -> L _ (HsValBinds _ (ValBinds _ binds _))) = allVars' (bagToList binds) -- Value bindings.
-  allVars' (dL -> L _ (HsIPBinds _ (IPBinds _ binds))) = allVars' binds -- Implicit parameter bindings.
+  -- allVars p@SplicePat{} = allVars $ children p -- Splice pattern (includes quasi-quotes).
+  -- allVars p@SigPat{} = allVars $ children p -- Pattern with a type signature.
+  -- allVars p@CoPat{} = allVars $ children p -- Coercion pattern.
+  -- allVars p@LazyPat{} = allVars $ children p -- Lazy pattern.
+  -- allVars p@ParPat{} = allVars $ children p -- Parenthesized pattern.
+  -- allVars p@BangPat{} = allVars $ children p -- Bang pattern.
+  -- allVars p@ListPat{} = allVars $ children p -- Syntactic list.
+  -- allVars p@TuplePat{} = allVars $ children p -- Tuple sub patterns.
+  -- allVars p@SumPat{} = allVars $ children p -- Anonymous sum pattern.
 
-  allVars' (dL -> L _ EmptyLocalBinds{}) =  mempty -- The case of no local bindings (signals the empty `let` or `where` clause).
+  allVars p = allVars $ children p
 
-  allVars' _ = mempty -- New ctor.
+instance AllVars (LocatedA (HsFieldBind (LocatedA (FieldOcc GhcPs)) (LocatedA (Pat GhcPs)))) where
+   allVars (L _ (HsFieldBind _ _ x _)) = allVars x
 
-instance AllVars' (LIPBind GhcPs) where
-  allVars' (dL -> L _ (IPBind _ _ e)) = freeVars_' e
+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 _ (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_ (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 (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' (LHsBind GhcPs) where
-  allVars' (dL -> L _ FunBind{fun_id=n, fun_matches=MG{mg_alts=(dL -> L _ ms)}}) = allVars' (VarPat noExt n :: 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' (dL -> 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'.
+instance AllVars (LocatedA (IPBind GhcPs)) where
+  allVars (L _ (IPBind _ _ e)) = freeVars_ e
 
-  allVars' (dL -> L _ (PatSynBind _ PSB{})) = mempty -- Come back to it.
-  allVars' (dL -> L _ VarBind{}) = mempty -- Typechecker.
-  allVars' (dL -> L _ AbsBinds{}) = mempty -- Not sure but I think renamer.
+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' _ = mempty -- New ctor.
+  allVars (L _ (PatSynBind _ PSB{})) = mempty -- Come back to it.
 
-instance AllVars' (MatchGroup GhcPs (LHsExpr GhcPs)) where
-  allVars' (MG _ _alts@(dL -> 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' (dL -> L _ (Match _ FunRhs {mc_fun=name} pats grhss)) = allVars' (VarPat noExt name :: Pat GhcPs) <> allVars' pats <> allVars' grhss -- A pattern matching on an argument of a function binding.
-  allVars' (dL -> L _ (Match _ (StmtCtxt ctxt) pats grhss)) = allVars' ctxt <> allVars' pats <> allVars' grhss -- Pattern of a do-stmt, list comprehension, pattern guard etc.
-  allVars' (dL -> L _ (Match _ _ pats grhss)) = inVars' (allVars' pats) (allVars' grhss) -- Everything else.
+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.
 
-  allVars' _ = mempty -- New ctor.
+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
 
-instance AllVars' (HsStmtContext RdrName) where
-  allVars' (PatGuard FunRhs{mc_fun=n}) = allVars' (VarPat noExt n :: Pat GhcPs)
-  allVars' ParStmtCtxt{} = mempty -- Come back to it.
-  allVars' TransStmtCtxt{}  = mempty -- Come back to it.
+instance AllVars (GRHSs GhcPs (LocatedA (HsExpr GhcPs))) where
+  allVars (GRHSs _ grhss binds) = inVars binds (mconcatMap allVars grhss)
 
-  allVars' _ = mempty -- Everything else (correct).
+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
 
-instance AllVars' (GRHSs GhcPs (LHsExpr GhcPs)) where
-  allVars' (GRHSs _ grhss binds) = inVars' binds (mconcat (map allVars' grhss))
+instance AllVars (LocatedA (HsDecl GhcPs)) where
+  allVars (L l (ValD _ bind)) = allVars (L l bind :: LocatedA (HsBindLR GhcPs GhcPs))
+  allVars _ = mempty
 
-  allVars' _ = mempty -- New ctor.
 
-instance AllVars' (LGRHS GhcPs (LHsExpr GhcPs)) where
-  allVars' (dL -> 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' (dL -> L l (ValD _ bind)) = allVars' (cL l bind :: LHsBind GhcPs)
-
-  allVars' _ = mempty  -- We only consider value bindings.
-
---
-
-vars' :: FreeVars' a => a -> [String]
-vars' = Set.toList . Set.map occNameString . freeVars'
-
-varss' :: AllVars' a => a -> [String]
-varss' = Set.toList . Set.map occNameString . free' . allVars'
+vars :: FreeVars a => a -> [String]
+vars = Set.toList . Set.map occNameString . freeVars
 
-pvars' :: AllVars' a => a -> [String]
-pvars' = Set.toList . Set.map occNameString . bound' . allVars'
+varss :: AllVars a => a -> [String]
+varss = Set.toList . Set.map occNameString . free . allVars
 
-freeVarSet' :: FreeVars' a => a -> Set String
-freeVarSet' = Set.map occNameString . freeVars'
+pvars :: AllVars a => a -> [String]
+pvars = Set.toList . Set.map occNameString . bound . allVars
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
@@ -1,19 +1,11 @@
 {-# LANGUAGE NamedFieldPuns #-}
 
-module GHC.Util.HsDecl (declName,bindName,isForD',isNewType',isDerivD',isClsDefSig')
+module GHC.Util.HsDecl (declName,bindName)
 where
 
-import HsSyn
-import OccName
-import SrcLoc
-
-isNewType' :: NewOrData -> Bool
-isNewType' NewType = True
-isNewType' DataType = False
-
-isForD', isDerivD' :: LHsDecl GhcPs -> Bool
-isForD' (LL _ ForD{}) = True; isForD' _ = False
-isDerivD' (LL _ DerivD{}) = True; isDerivD' _ = False
+import GHC.Hs
+import GHC.Types.SrcLoc
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
 -- | @declName x@ returns the \"new name\" that is created (for
 -- example a function declaration) by @x@.  If @x@ isn't a declaration
@@ -22,7 +14,7 @@
 -- want to tell users to rename binders that they aren't creating
 -- right now and therefore usually cannot change.
 declName :: LHsDecl GhcPs -> Maybe String
-declName (LL _ x) = occNameString . occName <$> case x of
+declName (L _ x) = occNameStr <$> case x of
     TyClD _ FamDecl{tcdFam=FamilyDecl{fdLName}} -> Just $ unLoc fdLName
     TyClD _ SynDecl{tcdLName} -> Just $ unLoc tcdLName
     TyClD _ DataDecl{tcdLName} -> Just $ unLoc tcdLName
@@ -36,13 +28,9 @@
     ForD _ ForeignImport{fd_name} -> Just $ unLoc fd_name
     ForD _ ForeignExport{fd_name} -> Just $ unLoc fd_name
     _ -> Nothing
-declName _ = Nothing {- COMPLETE LL-}
 
 
 bindName :: LHsBind GhcPs -> Maybe String
-bindName (LL _ FunBind{fun_id}) = Just $ occNameString $ occName $ unLoc fun_id
-bindName (LL _ VarBind{var_id}) = Just $ occNameString $ occName var_id
+bindName (L _ FunBind{fun_id}) = Just $ rdrNameStr fun_id
+bindName (L _ VarBind{var_id}) = Just $ occNameStr var_id
 bindName _ = Nothing
-
-isClsDefSig' :: Sig GhcPs -> Bool
-isClsDefSig' (ClassOpSig _ True _ _) = True; isClsDefSig' _ = False
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,271 +1,322 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE ViewPatterns, MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-}
+{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE TupleSections #-}
 
---  Keep until 'descendApps', 'transformApps' and 'allowLeftSection'
--- are used.
-{-# OPTIONS_GHC -Wno-unused-top-binds #-}
-
 module GHC.Util.HsExpr (
-    dotApp', dotApps'
-  , simplifyExp', niceLambda', niceDotApp'
-  , Brackets'(..)
-  , rebracket1', appsBracket', transformAppsM', fromApps', apps', universeApps', universeParentExp'
-  , paren'
-  , replaceBranches'
-  , needBracketOld', transformBracketOld', descendBracketOld', reduce', reduce1', fromParen1'
+    dotApps, lambda
+  , simplifyExp, niceLambda, niceLambdaR
+  , Brackets(..)
+  , rebracket1, appsBracket, transformAppsM, fromApps, apps, universeApps, universeParentExp
+  , paren
+  , replaceBranches
+  , needBracketOld, transformBracketOld, fromParen1
+  , allowLeftSection, allowRightSection
 ) where
 
-import HsSyn
-import BasicTypes
-import SrcLoc
-import FastString
-import RdrName
-import OccName
-import Bag(bagToList)
+import GHC.Hs
+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.View
 import GHC.Util.FreeVars
-import GHC.Util.Pat
+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.Data
+import Data.Generics.Uniplate.DataOnly
 import Data.List.Extra
 import Data.Tuple.Extra
+import Data.Maybe
 
-import Refact.Types hiding (Match)
-import qualified Refact.Types as R (SrcSpan)
+import Refact (substVars, toSSA)
+import Refact.Types hiding (SrcSpan, Match)
+import Refact.Types qualified as R (SrcSpan)
 
+import Language.Haskell.GhclibParserEx.GHC.Hs.Pat
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
 -- | 'dotApp a b' makes 'a . b'.
-dotApp' :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
-dotApp' x y = noLoc $ OpApp noExt x (noLoc $ HsVar noExt (noLoc $ mkVarUnqual (fsLit "."))) y
+dotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
+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"
-dotApps' [x] = x
-dotApps' (x : xs) = dotApp' x (dotApps' xs)
+dotApps :: [LHsExpr GhcPs] -> LHsExpr GhcPs
+dotApps [] = error "GHC.Util.HsExpr.dotApps', does not work on an empty list"
+dotApps [x] = x
+dotApps (x : xs) = dotApp x (dotApps xs)
 
+-- | @lambda [p0, p1..pn] body@ makes @\p1 p1 .. pn -> body@
+lambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs
+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
-paren' x
-  | isAtom' x  = x
-  | otherwise = addParen' x
+paren :: LHsExpr GhcPs -> LHsExpr GhcPs
+paren x
+  | isAtom x  = x
+  | otherwise = addParen x
 
-universeParentExp' :: Data a => a -> [(Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs)]
-universeParentExp' xs = concat [(Nothing, x) : f x | x <- childrenBi xs]
-    where f p = concat [(Just (i,p), c) : f c | (i,c) <- zip [0..] $ children p]
+universeParentExp :: Data a => a -> [(Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs)]
+universeParentExp xs = concat [(Nothing, x) : f x | x <- childrenBi xs]
+    where f p = concat [(Just (i,p), c) : f c | (i,c) <- zipFrom 0 $ children p]
 
 
-apps' :: [LHsExpr GhcPs] -> LHsExpr GhcPs
-apps' = foldl1' mkApp where mkApp x y = noLoc (HsApp noExt x y)
-
-fromApps' :: LHsExpr GhcPs  -> [LHsExpr GhcPs]
-fromApps' (LL _ (HsApp _ x y)) = fromApps' x ++ [y]
-fromApps' x = [x]
+apps :: [LHsExpr GhcPs] -> LHsExpr GhcPs
+apps = foldl1' mkApp where mkApp x y = noLocA (HsApp noExtField x y)
 
-childrenApps' :: LHsExpr GhcPs -> [LHsExpr GhcPs]
-childrenApps' (LL _ (HsApp _ x y)) = childrenApps' x ++ [y]
-childrenApps' x = children x
+fromApps :: LHsExpr GhcPs  -> [LHsExpr GhcPs]
+fromApps (L _ (HsApp _ x y)) = fromApps x ++ [y]
+fromApps x = [x]
 
-universeApps' :: LHsExpr GhcPs -> [LHsExpr GhcPs]
-universeApps' x = x : concatMap universeApps' (childrenApps' x)
+childrenApps :: LHsExpr GhcPs -> [LHsExpr GhcPs]
+childrenApps (L _ (HsApp _ x y)) = childrenApps x ++ [y]
+childrenApps x = children x
 
-descendApps' :: (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs
-descendApps' f (LL l (HsApp _ x y)) = LL l $ HsApp noExt (descendApps' f x) (f y)
-descendApps' f x = descend f x
+universeApps :: LHsExpr GhcPs -> [LHsExpr GhcPs]
+universeApps x = x : concatMap universeApps (childrenApps x)
 
-descendAppsM' :: Monad m => (LHsExpr GhcPs  -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs)
-descendAppsM' f (LL l (HsApp _ x y)) = liftA2 (\x y -> LL l $ HsApp noExt x y) (descendAppsM' f x) (f y)
-descendAppsM' f x = descendM f x
+descendAppsM :: Monad m => (LHsExpr GhcPs  -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs)
+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
 
-transformApps' :: (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs
-transformApps' f = f . descendApps' (transformApps' f)
+transformAppsM :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs)
+transformAppsM f x = f =<< descendAppsM (transformAppsM 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 = fst . descendIndex' (\x a -> writer (f x a, ()))
 
-descendIndex' :: Data a => (Int -> a -> a) -> a -> a
-descendIndex' f x = flip evalState 0 $ flip descendM x $ \y -> do
+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)
-    return $ f i y
+    lift $ f i y
 
 --  There are differences in pretty-printing between GHC and HSE. This
 --  version never removes brackets.
-descendBracket' :: (LHsExpr GhcPs -> (Bool, LHsExpr GhcPs)) -> LHsExpr GhcPs -> LHsExpr GhcPs
-descendBracket' op x = descendIndex' g x
+descendBracket :: (LHsExpr GhcPs -> (Bool, LHsExpr GhcPs)) -> LHsExpr GhcPs -> LHsExpr GhcPs
+descendBracket op x = descendIndex g x
     where
         g i y = if a then f i b else b
             where (a, b) = op y
-        f i y@(LL _ e) | needBracket' i x y = addParen' y
-        f _ y = y
+        f i y = if needBracket i x y then addParen y else y
 
--- Add brackets as suggested 'needBracket' at 1-level of depth.
-rebracket1' :: LHsExpr GhcPs -> LHsExpr GhcPs
-rebracket1' = descendBracket' (True, )
+-- Add brackets as suggested 'needBracket at 1-level of depth.
+rebracket1 :: LHsExpr GhcPs -> LHsExpr GhcPs
+rebracket1 = descendBracket (True, )
 
 -- A list of application, with any necessary brackets.
-appsBracket' :: [LHsExpr GhcPs] -> LHsExpr GhcPs
-appsBracket' = foldl1 mkApp
-  where mkApp x y = rebracket1' (noLoc $ HsApp noExt x y)
-
+appsBracket :: [LHsExpr GhcPs] -> LHsExpr GhcPs
+appsBracket = foldl1 mkApp
+  where mkApp x y = rebracket1 (noLocA $ HsApp noExtField x y)
 
-simplifyExp' :: LHsExpr GhcPs -> LHsExpr GhcPs
+simplifyExp :: LHsExpr GhcPs -> LHsExpr GhcPs
 -- Replace appliciations 'f $ x' with 'f (x)'.
-simplifyExp' (LL l (OpApp _ x op y)) | isDol op = LL l (HsApp noExt x (noLoc (HsPar noExt y)))
-simplifyExp' e@(LL _ (HsLet _ (LL _ (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
-    [LL _ (FunBind _ _(MG _ (LL _ [LL _ (Match _(FunRhs (LL _ x) _ _) [] (GRHSs _[LL _ (GRHS _ [] y)] (LL _ (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]'.
-      | occNameString (rdrNameOcc x) `notElem` vars' y && length [() | Unqual a <- universeBi z, a == rdrNameOcc x] <= 1 ->
+      | occNameStr x `notElem` vars y && length [() | Unqual a <- universeBi z, a == rdrNameOcc x] <= 1 ->
           transform f z
-          where f (view' -> Var_' x') | occNameString (rdrNameOcc x) == x' = paren' y
+          where f (view -> Var_ x') | occNameStr x == x' = paren y
                 f x = x
     _ -> e
-simplifyExp' e = e
+simplifyExp e = e
 
 -- Rewrite '($) . b' as 'b'.
-niceDotApp' :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
-niceDotApp' (LL _ (HsVar _ (L _ r))) b | occNameString (rdrNameOcc r) == "$" = b
-niceDotApp' a b = dotApp' a b
-
+niceDotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
+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.
+niceLambda :: [String] -> LHsExpr GhcPs -> LHsExpr GhcPs
+niceLambda ss e = fst (niceLambdaR ss e)-- We don't support refactorings yet.
 
+allowRightSection :: String -> Bool
 allowRightSection x = x `notElem` ["-","#"]
+allowLeftSection :: String -> Bool
 allowLeftSection x = x /= "#"
 
 -- Implementation. Try to produce special forms (e.g. sections,
 -- compositions) where we can.
-niceLambdaR' :: [String]
-             -> LHsExpr GhcPs
-             -> (LHsExpr GhcPs, R.SrcSpan
-             -> [Refactoring R.SrcSpan])
--- Rewrite '\xs -> (e)' as '\xs -> e'.
-niceLambdaR' xs (LL _ (HsPar _ x)) = niceLambdaR' xs x
--- Rewrite '\x -> x + a' as '(+ a)' (heuristic: 'a' must be a single
+niceLambdaR :: [String]
+            -> LHsExpr GhcPs
+            -> (LHsExpr GhcPs, R.SrcSpan -> [Refactoring R.SrcSpan])
+-- Rewrite @\ -> e@ as @e@
+-- These are encountered as recursive calls.
+niceLambdaR xs (SimpleLambda [] x) = niceLambdaR xs x
+
+-- Rewrite @\xs -> (e)@ as @\xs -> e@.
+niceLambdaR xs (L _ (HsPar _ x)) = niceLambdaR xs x
+
+-- @\vs v -> ($) e v@ ==> @\vs -> e@
+-- @\vs v -> e $ v@ ==> @\vs -> e@
+niceLambdaR (unsnoc -> Just (vs, v)) (view -> App2 f e (view -> Var_ v'))
+  | isDol f
+  , v == v'
+  , vars e `disjoint` [v]
+  = niceLambdaR vs e
+
+-- @\v -> thing + v@ ==> @\v -> (thing +)@  (heuristic: @v@ must be a single
+-- lexeme, or it all gets too complex)
+niceLambdaR [v] (L _ (OpApp _ e f (view -> Var_ v')))
+  | isLexeme e
+  , v == v'
+  , vars e `disjoint` [v]
+  , L _ (HsVar _ (L _ fname)) <- f
+  , isSymOcc $ rdrNameOcc fname
+  = let res = nlHsPar $ noLocA $ SectionL noExtField e f
+     in (res, \s -> [Replace Expr s [] (unsafePrettyPrint res)])
+
+-- @\vs v -> f x v@ ==> @\vs -> f x@
+niceLambdaR (unsnoc -> Just (vs, v)) (L _ (HsApp _ f (view -> Var_ v')))
+  | v == v'
+  , vars f `disjoint` [v]
+  = niceLambdaR vs f
+
+-- @\vs v -> (v `f`)@ ==> @\vs -> f@
+niceLambdaR (unsnoc -> Just (vs, v)) (L _ (SectionL _ (view -> Var_ v') f))
+  | v == v' = niceLambdaR vs f
+
+-- Strip one variable pattern from the end of a lambdas match, and place it in our list of factoring variables.
+niceLambdaR xs (SimpleLambda ((view -> PVar_ v):vs) x)
+  | v `notElem` xs = niceLambdaR (xs++[v]) $ lambda vs x
+
+-- Rewrite @\x -> x + a@ as @(+ a)@ (heuristic: @a@ must be a single
 -- lexeme, or it all gets too complex).
-niceLambdaR' [x] (view' -> App2' op@(LL _ (HsVar _ (L _ tag))) l r)
-  | isLexeme r, view' l == Var_' x, x `notElem` vars' r, allowRightSection (occNameString $ rdrNameOcc tag) =
-      let e = rebracket1' $ addParen' (noLoc $ SectionR noExt op r)
-      in (e, const [])
--- Rewrite (1) '\x -> f (b x)' as 'f . b', (2) '\x -> f $ b x' as 'f . b'.
-niceLambdaR' [x] y
-  | Just (z, subts) <- factor y, x `notElem` vars' z = (z, const [])
+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 (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
+  | Just (z, subts) <- factor y, x `notElem` vars z = (z, \s -> [mkRefact subts s])
   where
     -- Factor the expression with respect to x.
     factor :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [LHsExpr GhcPs])
-    factor y@(LL _ (HsApp _ ini lst)) | view' lst == Var_' x = Just (ini, [ini])
-    factor y@(LL _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst
-      = let r = niceDotApp' ini z
+    factor (L _ (HsApp _ ini lst)) | view lst == Var_ x = Just (ini, [ini])
+    factor (L _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst
+      = let r = niceDotApp ini z
         in if astEq r z then Just (r, ss) else Just (r, ini : ss)
-    factor (LL _ (OpApp _ y op (factor -> Just (z, ss))))| isDol op
-      = let r = niceDotApp' y z
+    factor (L _ (OpApp _ y op (factor -> Just (z, ss))))| isDol op
+      = let r = niceDotApp y z
         in if astEq r z then Just (r, ss) else Just (r, y : ss)
-    factor (LL _ (HsPar _ y@(LL _ HsApp{}))) = factor y
+    factor (L _ (HsPar _ y@(L _ HsApp{}))) = factor y
     factor _ = Nothing
--- Rewrite '\x y -> x + y' as '(+)'.
-niceLambdaR' [x,y] (LL _ (OpApp _ (view' -> Var_' x1) op@(LL _ HsVar {}) (view' -> Var_' y1)))
-    | x == x1, y == y1, vars' op `disjoint` [x, y] = (op, const [])
--- Rewrite '\x y -> f y x' as 'flip f'.
-niceLambdaR' [x, y] (view' -> App2' op (view' -> Var_' y1) (view' -> Var_' x1))
-  | x == x1, y == y1, vars' op `disjoint` [x, y] = (noLoc $ HsApp noExt (strToVar "flip") op, const [])
+    mkRefact :: [LHsExpr GhcPs] -> R.SrcSpan -> Refactoring R.SrcSpan
+    mkRefact subts s =
+      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 @(+)@.
+niceLambdaR [x,y] (L _ (OpApp _ (view -> Var_ x1) op@(L _ HsVar {}) (view -> Var_ y1)))
+    | x == x1, y == y1, vars op `disjoint` [x, y] = (op, \s -> [Replace Expr s [] (unsafePrettyPrint op)])
+-- Rewrite @\x y -> f y x@ as @flip f@.
+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", toSSA op)] (unsafePrettyPrint $ gen (strToVar "x"))]
+      )
+  where
+    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", toSSA e)] "a"])
 -- Base case. Just a good old fashioned lambda.
-niceLambdaR' ss e =
-  let grhs = noLoc $ GRHS noExt [] e :: LGRHS GhcPs (LHsExpr GhcPs)
-      grhss = GRHSs {grhssExt = noExt, grhssGRHSs=[grhs], grhssLocalBinds=noLoc $ EmptyLocalBinds noExt}
-      match = noLoc $ Match {m_ext=noExt, m_ctxt=LambdaExpr, m_pats=map strToPat' ss, m_grhss=grhss} :: LMatch GhcPs (LHsExpr GhcPs)
-      matchGroup = MG {mg_ext=noExt, mg_origin=Generated, mg_alts=noLoc [match]}
-  in (noLoc $ HsLam noExt matchGroup, const [])
+niceLambdaR ss e =
+  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' (LL l (HsIf _ _ a b c)) = ([b, c], \[b, c] -> cL l (HsIf noExt Nothing a b c))
+replaceBranches :: LHsExpr GhcPs -> ([LHsExpr GhcPs], [LHsExpr GhcPs] -> LHsExpr GhcPs)
+replaceBranches (L l (HsIf _ a b c)) = ([b, c], \[b, c] -> L l (HsIf noAnn a b c))
 
-replaceBranches' (LL s (HsCase _ a (MG _ (L l bs) FromSource))) =
-  (concatMap f bs, \xs -> cL s (HsCase noExt a (MG noExt (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 (LL _ (Match _ CaseAlt _ (GRHSs _ xs _))) = [x | (LL _ (GRHS _ _ x)) <- xs]
-    f _ = undefined -- {-# COMPLETE LL #-}
+    f (L _ (Match _ CaseAlt _ (GRHSs _ xs _))) = [x | (L _ (GRHS _ _ x)) <- xs]
+    f _ = error "GHC.Util.HsExpr.replaceBranches: unexpected XMatch"
 
     g :: [LMatch GhcPs (LHsExpr GhcPs)] -> [LHsExpr GhcPs] -> [LMatch GhcPs (LHsExpr GhcPs)]
-    g (LL s1 (Match _ CaseAlt a (GRHSs _ ns b)) : rest) xs =
-      cL s1 (Match noExt CaseAlt a (GRHSs noExt [cL a (GRHS noExt gs x) | (LL a (GRHS _ gs _), x) <- zip ns as] b)) : g rest bs
+    g (L s1 (Match _ CaseAlt a (GRHSs _ ns b)) : rest) xs =
+      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"
 
-replaceBranches' x = ([], \[] -> x)
+replaceBranches x = ([], \[] -> x)
 
 
--- Like needBracket', but with a special case for 'a . b . b', which was
+-- Like needBracket, but with a special case for 'a . b . b', which was
 -- removed from haskell-src-exts-util-0.2.2.
-needBracketOld' :: Int -> LHsExpr GhcPs -> LHsExpr GhcPs -> Bool
-needBracketOld' i parent child
+needBracketOld :: Int -> LHsExpr GhcPs -> LHsExpr GhcPs -> Bool
+needBracketOld i parent child
   | isDotApp parent, isDotApp child, i == 2 = False
-  | otherwise = needBracket' i parent child
+  | otherwise = needBracket i parent child
 
-transformBracketOld' :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs)
-transformBracketOld' op = first snd . g
+transformBracketOld :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs))
+                    -> LHsExpr GhcPs
+                    -> (LHsExpr GhcPs, (LHsExpr GhcPs, [String]))
+transformBracketOld op = first snd . g
   where
-    g = first f . descendBracketOld' g
+    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
-
-    g1 = (fst .) . g
-    g2 = (snd .) . g
-
-    f i (LL _ (HsPar _ y)) z | not $ needBracketOld' i x y = (y, z)
-    f i y z                  | needBracketOld' i x y = (addParen' y, addParen' z)
-    f _ y z                  = (y, z)
+    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
 
-    f1 = ((fst .) .) . f
-    f2 = ((snd .) .) . f
+    g1 a b = fst (g a b)
+    g2 a b = writer $ snd (g a b)
 
-reduce' :: LHsExpr GhcPs -> LHsExpr GhcPs
-reduce' = fromParen' . transform reduce1'
+    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, w))
+    f _ y z w = (y, (z, w))
 
-reduce1' :: LHsExpr GhcPs -> LHsExpr GhcPs
-reduce1' (LL loc (HsApp _ len (LL _ (HsLit _ (HsString _ xs)))))
-  | varToStr len == "length" = cL loc $ HsLit noExt (HsInt noExt (IL NoSourceText False n))
-  where n = fromIntegral $ length (unpackFS xs)
-reduce1' (LL loc (HsApp _ len (LL _ (ExplicitList _ _ xs))))
-  | varToStr len == "length" = cL loc $ HsLit noExt (HsInt noExt (IL NoSourceText False n))
-  where n = fromIntegral $ length xs
-reduce1' (view' -> App2' op (LL _ (HsLit _ x)) (LL _ (HsLit _ y))) | varToStr op == "==" = strToVar (show (astEq x y))
-reduce1' (view' -> App2' op (LL _ (HsLit _ (HsInt _ x))) (LL _ (HsLit _ (HsInt _ y)))) | varToStr op == ">=" = strToVar $ show (x >= y)
-reduce1' (view' -> App2' op x y)
-    | varToStr op == "&&" && varToStr x == "True"  = y
-    | varToStr op == "&&" && varToStr x == "False" = x
-reduce1' (LL _ (HsPar _ x)) | isAtom' x = x
-reduce1' x = x
+    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
+      _ -> False
 
-fromParen1' :: LHsExpr GhcPs -> LHsExpr GhcPs
-fromParen1' (LL _ (HsPar _ x)) = x
-fromParen1' x = x
+fromParen1 :: LHsExpr GhcPs -> LHsExpr GhcPs
+fromParen1 x = fromMaybe x $ remParen x
diff --git a/src/GHC/Util/HsType.hs b/src/GHC/Util/HsType.hs
deleted file mode 100644
--- a/src/GHC/Util/HsType.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-
-module GHC.Util.HsType (
-    Brackets'(..)
-  , fromTyParen'
-  , isTyQuasiQuote'
-  , isUnboxedTuple'
-  ) where
-
-import HsSyn
-import SrcLoc
-
-import GHC.Util.Brackets
-
-fromTyParen' :: LHsType GhcPs -> LHsType GhcPs
-fromTyParen' (LL _ (HsParTy _ x)) = x
-fromTyParen' x = x
-
-isTyQuasiQuote' :: LHsType GhcPs -> Bool
-isTyQuasiQuote' (LL _ (HsSpliceTy _ HsQuasiQuote{})) = True; isTyQuasiQuote' _ = False
-
-isUnboxedTuple' :: HsTupleSort -> Bool
-isUnboxedTuple' HsUnboxedTuple = True
-isUnboxedTuple' _ = False
diff --git a/src/GHC/Util/LanguageExtensions/Type.hs b/src/GHC/Util/LanguageExtensions/Type.hs
deleted file mode 100644
--- a/src/GHC/Util/LanguageExtensions/Type.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-
-module GHC.Util.LanguageExtensions.Type (
-    readExtension
-) where
-
-import GHC.LanguageExtensions.Type
-
-import qualified Data.Map.Strict as Map
-
--- | Parse a GHC extension
-readExtension :: String -> Maybe Extension
-readExtension x = Map.lookup x exts
-  where exts = Map.fromList [(show x, x) | x <- [Cpp .. StarIsType]]
diff --git a/src/GHC/Util/Module.hs b/src/GHC/Util/Module.hs
deleted file mode 100644
--- a/src/GHC/Util/Module.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-
-module GHC.Util.Module (modName, fromModuleName') where
-
-import HsSyn
-import Module
-import SrcLoc
-
-modName :: Located (HsModule GhcPs) -> String
-modName (LL _ HsModule {hsmodName=Nothing}) = "Main"
-modName (LL _ HsModule {hsmodName=Just (L _ n)}) = moduleNameString n
-modName _ = "" -- {-# COMPLETE LL #-}
-
-fromModuleName' :: Located ModuleName -> String
-fromModuleName' (LL _ n) = moduleNameString n
-fromModuleName' _ = "" -- {# COMPLETE LL #}
diff --git a/src/GHC/Util/Outputable.hs b/src/GHC/Util/Outputable.hs
deleted file mode 100644
--- a/src/GHC/Util/Outputable.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-
-module GHC.Util.Outputable (unsafePrettyPrint) where
-
-import Outputable
-
--- \"Unsafe\" in this case means that it uses the following
--- 'DynFlags' for printing -
--- <http://hackage.haskell.org/package/ghc-lib-parser-8.8.0.20190424/docs/src/DynFlags.html#v_unsafeGlobalDynFlags
--- unsafeGlobalDynFlags> This could lead to the issues documented
--- there, but it also might not be a problem for our use case.  TODO:
--- Decide whether this really is unsafe, and if it is, what needs to
--- be done to make it safe.
-unsafePrettyPrint :: (Outputable.Outputable a) => a -> String
-unsafePrettyPrint = Outputable.showSDocUnsafe . Outputable.ppr
diff --git a/src/GHC/Util/Pat.hs b/src/GHC/Util/Pat.hs
deleted file mode 100644
--- a/src/GHC/Util/Pat.hs
+++ /dev/null
@@ -1,58 +0,0 @@
-{-# LANGUAGE MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-}
-
-module GHC.Util.Pat (
-    strToPat', patToStr'
-  , Brackets'(..)
-  , fromPChar', isPFieldWildcard', hasPFieldsDotDot', isPWildCard'
-  , isPFieldPun', isPatTypeSig', isPBangPat', isPViewPat'
-  ) where
-
-import HsSyn
-import SrcLoc
-import TysWiredIn
-import FastString
-import RdrName
-
-import GHC.Util.Brackets
-
-patToStr' :: Pat GhcPs -> String
-patToStr' (LL _ (ConPatIn (L _ x) (PrefixCon []))) | x == true_RDR = "True"
-patToStr' (LL _ (ConPatIn (L _ x) (PrefixCon []))) | x == false_RDR = "False"
-patToStr' (LL _ (ConPatIn (L _ x) (PrefixCon []))) | x == nameRdrName nilDataConName = "[]"
-patToStr' _ = ""
-
-strToPat' :: String -> Pat GhcPs
-strToPat' z
-  | z == "True"  = ConPatIn (noLoc true_RDR) (PrefixCon [])
-  | z == "False" = ConPatIn (noLoc false_RDR) (PrefixCon [])
-  | z == "[]"    = ConPatIn (noLoc $ nameRdrName nilDataConName) (PrefixCon [])
-  | otherwise    = VarPat noExt (noLoc $ mkVarUnqual (fsLit z))
-
-fromPChar' :: Pat GhcPs -> Maybe Char
-fromPChar' (LL _ (LitPat _ (HsChar _ x))) = Just x
-fromPChar' _ = Nothing
-
--- Contains a '..' as in 'Foo{..}'
-hasPFieldsDotDot' :: HsRecFields GhcPs (Pat GhcPs) -> Bool
-hasPFieldsDotDot' HsRecFields {rec_dotdot=Just _} = True
-hasPFieldsDotDot' _ = False -- {-# COMPLETE LL #-}
-
--- Field has a '_' as in '{foo=_} or is punned e.g. '{foo}'.
-isPFieldWildcard' :: LHsRecField GhcPs (Pat GhcPs) -> Bool
-isPFieldWildcard' (LL _ HsRecField {hsRecFieldArg=(LL _ (WildPat _))}) = True
-isPFieldWildcard' (LL _ HsRecField {hsRecPun=True}) = True
-isPFieldWildcard' (LL _ HsRecField {}) = False
-isPFieldWildcard' _ = False -- {-# COMPLETE LL #-}
-
-isPWildCard' :: Pat GhcPs -> Bool
-isPWildCard' (LL _ (WildPat _)) = True
-isPWildCard' _ = False
-
-isPFieldPun' :: LHsRecField GhcPs (Pat GhcPs) -> Bool
-isPFieldPun' (LL _ HsRecField {hsRecPun=True}) = True
-isPFieldPun' _ = False
-
-isPatTypeSig', isPBangPat', isPViewPat' :: Pat GhcPs -> Bool
-isPatTypeSig' (LL _ SigPat{}) = True; isPatTypeSig' _ = False
-isPBangPat' (LL _ BangPat{}) = True; isPBangPat' _ = False
-isPViewPat' (LL _ ViewPat{}) = True; isPViewPat' _ = False
diff --git a/src/GHC/Util/RdrName.hs b/src/GHC/Util/RdrName.hs
deleted file mode 100644
--- a/src/GHC/Util/RdrName.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module GHC.Util.RdrName (isSpecial', unqual', rdrNameStr',fromQual') where
-
-import SrcLoc
-import Name
-import RdrName
-
-rdrNameStr' :: Located RdrName -> String
-rdrNameStr' = occNameString . rdrNameOcc . unLoc
-
--- Builtin type or data constructors.
-isSpecial' :: Located RdrName -> Bool
-isSpecial' (L _ (Exact n)) = isDataConName n || isTyConName n
-isSpecial' _ = False
-
--- Coerce qualified names to unqualified (by discarding the
--- qualifier).
-unqual' :: Located RdrName -> Located RdrName
-unqual' (L loc (Qual _ n)) = cL loc $ mkRdrUnqual n
-unqual' x = x
-
-fromQual' :: Located RdrName -> Maybe OccName
-fromQual' (L _ (Qual _ x)) = Just x
-fromQual' (L _ (Unqual x)) = Just x
-fromQual' _ = Nothing
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,125 +1,142 @@
-{-# LANGUAGE ViewPatterns #-}
+
+{-# Language ViewPatterns #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
 module GHC.Util.Scope (
-   Scope'
-  ,scopeCreate',scopeImports',scopeMatch',scopeMove'
+   Scope
+  ,scopeCreate,scopeMatch,scopeMove,possModules
 ) where
 
-import HsSyn
-import SrcLoc
-import BasicTypes
-import Module
-import FastString
-import RdrName
-import OccName
+import GHC.Hs
+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 GHC.Util.Module
-import GHC.Util.RdrName
-import Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
-import Data.List
 import Data.List.Extra
 import Data.Maybe
+import Data.Bifunctor
 
 -- A scope is a list of import declarations.
-newtype Scope' = Scope' [LImportDecl GhcPs]
-               deriving (Outputable, Monoid, Semigroup)
+newtype Scope = Scope [LImportDecl GhcPs]
+               deriving (Monoid, Semigroup)
 
--- Create a 'Scope' from a module's import declarations.
-scopeCreate' :: HsModule GhcPs -> Scope'
-scopeCreate' xs = Scope' $ [prelude | not $ any isPrelude res] ++ res
+instance Show Scope where
+    show (Scope x) = unsafePrettyPrint x
+
+-- Create a 'Scope from a module's import declarations.
+scopeCreate :: HsModule GhcPs -> Scope
+scopeCreate xs = Scope $ [prelude | not $ any isPrelude res] ++ res
   where
     -- Package qualifier of an import declaration.
     pkg :: LImportDecl GhcPs -> Maybe StringLiteral
-    pkg (LL _ x) = ideclPkgQual x
-    pkg _  = Nothing -- {-# COMPLETE LL #-}
+    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
-    isPrelude (LL _ x) = fromModuleName' (ideclName x) == "Prelude"
-    isPrelude _ = False -- {-# COMPLETE LL #-}
-
--- Access the imports in scope 'x'.
-scopeImports' :: Scope' -> [LImportDecl GhcPs]
-scopeImports' (Scope' x) = x
+    isPrelude (L _ x) = moduleNameString (unLoc (ideclName x)) == "Prelude"
 
 -- Test if two names in two scopes may be referring to the same
 -- 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' (a, x) (b, y)
-  | isSpecial' x && isSpecial' y = rdrNameStr' x == rdrNameStr' y
-  | isSpecial' x || isSpecial' y = False
+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
   | otherwise =
-     rdrNameStr' (unqual' x) == rdrNameStr' (unqual' y) && not (null $ possModules' a x `intersect` possModules' b y)
+     rdrNameStr (unqual x) == rdrNameStr (unqual y) && not (possModules a x `disjointOrd` possModules b y)
 
 -- 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' (a, x@(fromQual' -> Just name)) (Scope' b) = case imps of
-  [] -> head $ real ++ [x]
-  imp:_ | all ideclQualified imps -> noLoc $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name
-        | otherwise -> unqual' x
+scopeMove :: (Scope, LocatedN RdrName) -> Scope -> LocatedN RdrName
+scopeMove (a, x@(fromQual -> Just name)) (Scope b) = case imps of
+  [] | -- 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 (mkModuleName 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]
-scopeMove' (_, x) _ = x
+    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 -> [String]
-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 :: [String]
-    res = [fromModuleName' $ 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 -> [String]
-    f n | isSpecial' n = [""]
-    f (L _ (Qual mod _)) = [moduleNameString 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' (LL _ i) (L _ (Qual mod x)) =
-  moduleNameString mod `elem` map fromModuleName' ms && possImport' (noLoc i{ideclQualified=False}) (noLoc $ mkRdrUnqual x)
-  where ms = ideclName i : maybeToList (ideclAs i)
-possImport' (LL _ i) (L _ (Unqual x)) = not (ideclQualified i) && maybe True f (ideclHiding i)
+possImport :: LImportDecl GhcPs -> LocatedN RdrName -> IsImported
+possImport i n | isSpecial n = NotImported
+possImport (L _ i) (L _ (Qual mod 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)) =
+  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 -- {-# COMPLETE LL #-}
+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.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,32 +1,34 @@
-{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables, TupleSections #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-}
+{-# LANGUAGE DataKinds #-}
 
 module GHC.Util.Unify(
-    Subst', fromSubst',
-    validSubst', substitute',
-    unifyExp'
+    Subst(..), fromSubst,
+    validSubst, removeParens, substitute,
+    unifyExp
     ) where
 
+import Control.Applicative
 import Control.Monad
-import Data.Generics.Uniplate.Operations
+import Data.Generics.Uniplate.DataOnly
 import Data.Char
-import Data.List.Extra
 import Data.Data
-import Data.Tuple.Extra
+import Data.List.Extra
 import Util
 
-import HsSyn
-import SrcLoc as GHC
-import Outputable hiding ((<>))
-import RdrName
-import OccName
+import GHC.Hs
+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
-import GHC.Util.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import GHC.Util.HsExpr
-import GHC.Util.Pat
-import GHC.Util.RdrName
 import GHC.Util.View
+import Data.Maybe
+import GHC.Data.FastString
 
 isUnifyVar :: String -> Bool
 isUnifyVar [x] = x == '?' || isAlpha x
@@ -38,148 +40,262 @@
 
 -- A list of substitutions. A key may be duplicated, you need to call
 --  'check' to ensure the substitution is valid.
-newtype Subst' a = Subst' [(String, a)]
-    deriving (Semigroup, Monoid)
+newtype Subst a = Subst [(String, a)]
+    deriving (Semigroup, Monoid, Functor)
 
 -- Unpack the substitution.
-fromSubst' :: Subst' a -> [(String, a)]
-fromSubst' (Subst' xs) = xs
-
-instance Functor Subst' where
-    fmap f (Subst' xs) = Subst' $ map (second f) xs -- Interesting.
+fromSubst :: Subst a -> [(String, a)]
+fromSubst (Subst xs) = xs
 
-instance Outputable a => Show (Subst' a) where
-    show (Subst' xs) = unlines [a ++ " = " ++ unsafePrettyPrint b | (a,b) <- xs]
+instance Outputable a => Show (Subst a) where
+    show (Subst xs) = unlines [a ++ " = " ++ unsafePrettyPrint b | (a,b) <- xs]
 
 -- Check the unification is valid and simplify it.
-validSubst' :: (a -> a -> Bool) -> Subst' a -> Maybe (Subst' a)
-validSubst' eq = fmap Subst' . mapM f . groupSort . fromSubst'
+validSubst :: (a -> a -> Bool) -> Subst a -> Maybe (Subst a)
+validSubst eq = fmap Subst . mapM f . groupSort . fromSubst
     where f (x, y : ys) | all (eq y) ys = Just (x, y)
           f _ = Nothing
 
--- 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)
-substitute' (Subst' bind) = transformBracketOld' exp . transformBi pat . transformBi typ
+-- Remove unnecessary brackets from a Subst. The first argument is a list of unification variables
+-- for which brackets should be removed from their substitutions.
+removeParens :: [String] -> Subst (LHsExpr GhcPs) -> Subst (LHsExpr GhcPs)
+removeParens noParens (Subst xs) = Subst $
+  map (\(x, y) -> if x `elem` noParens then (x, fromParen y) else (x, y)) xs
+
+-- 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)
     -- Variables.
-    exp (LL _ (HsVar _ x)) = lookup (rdrNameStr' x) bind
+    exp (L _ (HsVar _ x)) = lookup (rdrNameStr x) bind
     -- Operator applications.
-    exp (LL loc (OpApp _ lhs (LL _ (HsVar _ x)) rhs))
-      | Just y <- lookup (rdrNameStr' x) bind = Just (cL loc (OpApp noExt lhs y rhs))
+    exp (L loc (OpApp _ lhs (L _ (HsVar _ x)) rhs))
+      | Just y <- lookup (rdrNameStr x) bind = Just (L loc (OpApp noExtField lhs y rhs))
     -- Left sections.
-    exp (LL loc (SectionL _ exp (LL _ (HsVar _ x))))
-      | Just y <- lookup (rdrNameStr' x) bind = Just (cL loc (SectionL noExt exp y))
+    exp (L loc (SectionL _ exp (L _ (HsVar _ x))))
+      | Just y <- lookup (rdrNameStr x) bind = Just (L loc (SectionL noExtField exp y))
     -- Right sections.
-    exp (LL loc (SectionR _ (LL _ (HsVar _ x)) exp))
-      | Just y <- lookup (rdrNameStr' x) bind = Just (cL loc (SectionR noExt y exp))
+    exp (L loc (SectionR _ (L _ (HsVar _ x)) exp))
+      | Just y <- lookup (rdrNameStr x) bind = Just (L loc (SectionR noExtField y exp))
     exp _ = Nothing
 
     pat :: LPat GhcPs -> LPat GhcPs
     -- Pattern variables.
-    pat (LL _ (VarPat _ x))
-      | Just y@(LL _ HsVar{}) <- lookup (rdrNameStr' x) bind = strToPat' (varToStr y)
+    pat (L _ (VarPat _ x))
+      | Just y@(L _ HsVar{}) <- lookup (rdrNameStr x) bind = strToPat $ varToStr y
     pat x = x :: LPat GhcPs
 
     typ :: LHsType GhcPs -> LHsType GhcPs
     -- Type variables.
-    typ (LL _ (HsTyVar _ _ x))
-      | Just (LL _ (HsAppType _ _ (HsWC _ y))) <- lookup (rdrNameStr' x) bind = y
+    typ (L _ (HsTyVar _ _ x))
+      | Just (L _ (HsAppType _ _ (HsWC _ y))) <- lookup (rdrNameStr x) bind = y
     typ x = x :: LHsType GhcPs
 
 
 ---------------------------------------------------------------------
 -- 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@.
-unify' :: Data a => NameMatch' -> Bool -> a -> a -> Maybe (Subst' (LHsExpr GhcPs))
+unify' :: Data a => NameMatch -> Bool -> a -> a -> Maybe (Subst (LHsExpr GhcPs))
 unify' nm root x y
     | Just (x, y) <- cast (x, y) = unifyExp' nm root x y
     | Just (x, y) <- cast (x, y) = unifyPat' nm x y
     | Just (x, y) <- cast (x, y) = unifyType' nm x y
-    | Just (x :: GHC.SrcSpan) <- cast x = Just mempty
+    | Just (x, y) <- cast (x, y) = if (x :: FastString) == y then Just mempty else Nothing
+
+    -- 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' :: Data a => NameMatch -> a -> a -> Maybe (Subst (LHsExpr GhcPs))
+unifyDef' nm x y =
+  fmap mconcat . sequence =<< gzip (unify' nm False) x y
 
+unifyComposed' :: NameMatch
+               -> LHsExpr GhcPs
+               -> LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs
+               -> Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs))
+unifyComposed' nm x1 y11 dot y12 =
+  ((, Just y11) <$> unifyExp' nm False x1 y12)
+    <|> case y12 of
+          (L _ (OpApp _ y121 dot' y122)) | isDot dot' ->
+            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,
+-- delegate to unifyExp'. These are the cases where we potentially need to call
+-- unifyComposed' to handle left composition.
+--
+-- y is allowed to partially match x (the lhs of the hint), if y is a function application where
+-- the function is a composition of functions. In this case the second component of the result is
+-- the unmatched part of y, which will be attached to the rhs of the hint after substitution.
+--
+-- Example:
+--   x = head (drop n x)
+--   y = foo . bar . baz . head $ drop 2 xs
+--   result = (Subst [(n, 2), (x, xs)], Just (foo . bar . baz))
+unifyExp :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs))
+-- Match wildcard operators.
+unifyExp nm root (L _ (OpApp _ lhs1 (L _ (HsVar _ (rdrNameStr -> v))) rhs1))
+                 (L _ (OpApp _ lhs2 (L _ (HsVar _ (rdrNameStr -> op2))) rhs2))
+    | isUnifyVar v =
+        (, Nothing) . (Subst [(v, strToVar op2)] <>) <$>
+        liftA2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2)
+
+-- Options: match directly, and expand through '.'
+unifyExp nm root x@(L _ (HsApp _ x1 x2)) (L _ (HsApp _ y1 y2)) =
+    ((, Nothing) <$> liftA2 (<>) (unifyExp' nm False x1 y1) (unifyExp' nm False x2 y2)) <|> unifyComposed
+  where
+    -- Unify a function application where the function is a composition of functions.
+    unifyComposed
+      | (L _ (OpApp _ y11 dot y12)) <- fromParen y1, isDot dot =
+          if not root then
+              -- 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 (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',
+              -- and so on, unify the rhs of '$' with 'x', and store the lhs of '$' into 'extra'.
+              -- You can only add to extra if you are at the root (otherwise 'extra' has nowhere to go).
+              rhs <- unifyExp' nm False x2 y2
+              (lhs, extra) <- unifyComposed' nm x1 y11 dot y12
+              pure (lhs <> rhs, extra)
+      | otherwise = Nothing
+
+-- Options: match directly, then expand through '$', then desugar infix.
+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 $ 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
+          addPar x = if isAtom x then x else addParen x
+
+unifyExp nm root x y = (, Nothing) <$> unifyExp' nm root x y
+
+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))
+noExtra (Just (x, Nothing)) = Just x
+noExtra _ = Nothing
+
 -- App/InfixApp are analysed specially for performance reasons. If
 -- 'root = True', this is the outside of the expr. Do not expand out a
 -- 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) )
+unifyExp' :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs))
+-- 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
+
 -- Brackets are not added when expanding '$' in user code, so tolerate
 -- them in the match even if they aren't in the user code.
-unifyExp' nm root x y | not root, isPar x, not $ isPar y = unifyExp' nm root (fromParen' x) y
--- Don't subsitute for type apps, since no one writes rules imaginging
--- they exist.
-unifyExp' nm root (LL _ (HsVar _ (rdrNameStr' -> v))) y | isUnifyVar v, not $ isTypeApp y = Just $ Subst' [(v, y)]
-unifyExp' nm root (LL _ (HsVar _ x)) (LL _ (HsVar _ y)) | nm x y = Just mempty
+-- Also, allow the user to put in more brackets than they strictly need (e.g. with infix).
+unifyExp' nm root x y | not root, isJust x2 || isJust y2 = unifyExp' nm root (fromMaybe x x2) (fromMaybe y y2)
+    where
+        -- Make sure we deal with the weird brackets that can't be removed around sections
+        x2 = remParen x
+        y2 = remParen y
 
--- Match wildcard operators.
-unifyExp' nm root (LL _ (OpApp _ lhs1 (LL _ (HsVar _ (rdrNameStr' -> v))) rhs1))
-                  (LL _ (OpApp _ lhs2 (LL _ (HsVar _ (rdrNameStr' -> op2))) rhs2))
-    | isUnifyVar v =
-        (Subst' [(v, strToVar op2)] <>) <$>
-        liftM2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2)
-unifyExp' nm root (LL _ (SectionL _ exp1 (LL _ (HsVar _ (rdrNameStr' -> v)))))
-                  (LL _ (SectionL _ exp2 (LL _ (HsVar _ (rdrNameStr' -> op2)))))
-    | isUnifyVar v = (Subst' [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2
-unifyExp' nm root (LL _ (SectionR _ (LL _ (HsVar _ (rdrNameStr' -> v))) exp1))
-                  (LL _ (SectionR _ (LL _ (HsVar _ (rdrNameStr' -> op2))) exp2))
-    | isUnifyVar v = (Subst' [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2
+unifyExp' nm root x@(L _ (OpApp _ lhs1 (L _ (HsVar _ (rdrNameStr -> v))) rhs1))
+                  y@(L _ (OpApp _ lhs2 (L _ (HsVar _ op2)) rhs2)) =
+  noExtra $ unifyExp nm root x y
+unifyExp' nm root (L _ (SectionL _ exp1 (L _ (HsVar _ (rdrNameStr -> v)))))
+                  (L _ (SectionL _ exp2 (L _ (HsVar _ (rdrNameStr -> op2)))))
+    | isUnifyVar v = (Subst [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2
+unifyExp' nm root (L _ (SectionR _ (L _ (HsVar _ (rdrNameStr -> v))) exp1))
+                  (L _ (SectionR _ (L _ (HsVar _ (rdrNameStr -> op2))) exp2))
+    | isUnifyVar v = (Subst [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2
 
--- Options: match directly, and expand through '.'
-unifyExp' nm root x@(LL _ (HsApp _ x1 x2)) (LL _ (HsApp _ y1 y2)) =
-    liftM2 (<>) (unifyExp' nm False x1 y1) (unifyExp' nm False x2 y2) `mplus`
-    (do guard $ not root
-            -- Don't expand '.' f at the root, otherwise you can get
-            -- duplicate matches because the matching engine
-            -- auto-generates hints in dot-form.
-        (LL _ (OpApp _ y11 dot y12)) <- return $ fromParen' y1
-        guard $ isDot dot
-        unifyExp' nm root x (noLoc (HsApp noExt y11 (noLoc (HsApp noExt y12 y2))))
-    )
+unifyExp' nm root x@(L _ (HsApp _ x1 x2)) y@(L _ (HsApp _ y1 y2)) =
+  noExtra $ unifyExp nm root x y
 
--- Options: match directly, then expand through '$', then desugar infix.
-unifyExp' nm root x (LL _ (OpApp _ lhs2 op2@(LL _ (HsVar _ op2')) rhs2))
-    | (LL _ (OpApp _ lhs1 op1@(LL _ (HsVar _ op1')) rhs1)) <- x = guard (nm op1' op2') >> liftM2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2)
-    | isDol op2 = unifyExp' nm root x $ noLoc (HsApp noExt lhs2 rhs2)
-    | otherwise  = unifyExp' nm root x $ noLoc (HsApp noExt (noLoc (HsApp noExt op2 lhs2)) rhs2)
+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.
         {-# INLINE isOther #-}
         isOther :: LHsExpr GhcPs -> Bool
-        isOther (LL _ HsVar{}) = False
-        isOther (LL _ HsApp{}) = False
-        isOther (LL _ OpApp{}) = False
+        isOther (L _ HsVar{}) = False
+        isOther (L _ HsApp{}) = False
+        isOther (L _ OpApp{}) = False
         isOther _ = True
 
 unifyExp' _ _ _ _ = Nothing
 
 
-unifyPat' :: NameMatch' -> LPat GhcPs -> LPat GhcPs -> Maybe (Subst' (LHsExpr GhcPs))
-unifyPat' nm (LL _ (VarPat _ x)) (LL _ (VarPat _ y)) =
-  Just $ Subst' [(rdrNameStr' x, strToVar(rdrNameStr' y))]
-unifyPat' nm (LL _ (VarPat _ x)) (LL _ (WildPat _)) =
-  let s = rdrNameStr' x in Just $ Subst' [(s, strToVar("_" ++ s))]
-unifyPat' nm (LL _ (ConPatIn x _)) (LL _ (ConPatIn y _)) | rdrNameStr' x /= rdrNameStr' y =
+unifyPat' :: NameMatch -> LPat GhcPs -> LPat GhcPs -> Maybe (Subst (LHsExpr GhcPs))
+unifyPat' nm (L _ (VarPat _ x)) (L _ (VarPat _ y)) =
+  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 _ (ConPat _ x _)) (L _ (ConPat _ y _)) | rdrNameStr x /= rdrNameStr y =
   Nothing
 unifyPat' nm x y =
   unifyDef' nm x y
 
-unifyType' :: NameMatch' -> LHsType GhcPs -> LHsType GhcPs -> Maybe (Subst' (LHsExpr GhcPs))
-unifyType' nm (LL loc (HsTyVar _ _ x)) y =
-  let wc = HsWC noExt y :: LHsWcType (NoGhcTc GhcPs)
-      unused = noLoc (HsVar noExt (noLoc $ mkRdrUnqual (mkVarOcc "__unused__"))) :: LHsExpr GhcPs
-      appType = cL loc (HsAppType noExt unused wc) :: LHsExpr GhcPs
- in Just $ Subst' [(rdrNameStr' x, appType)]
+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__"
+      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
@@ -1,55 +1,65 @@
-{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances #-}
+{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, PatternSynonyms #-}
 
 module GHC.Util.View (
-   fromParen', fromPParen'
-  , View'(..)
-  , Var_'(Var_'), PVar_'(PVar_'), PApp_'(PApp_'), App2'(App2'),LamConst1'(LamConst1')
+   fromParen
+  , View(..)
+  , RdrName_(RdrName_), Var_(Var_), PVar_(PVar_), PApp_(PApp_), App2(App2),LamConst1(LamConst1)
+  , pattern SimpleLambda
 ) where
 
-import HsSyn
-import SrcLoc
-import RdrName
-import OccName
-import BasicTypes
+import GHC.Hs
+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' (LL _ (HsPar _ x)) = fromParen' x
-fromParen' x = x
+fromParen :: LocatedA (HsExpr GhcPs) -> LocatedA (HsExpr GhcPs)
+fromParen x = maybe x fromParen $ remParen x
 
-fromPParen' :: Pat GhcPs -> Pat GhcPs
-fromPParen' (LL _ (ParPat _ x)) = fromPParen' x
-fromPParen' x = x
+fromPParen :: LocatedA (Pat GhcPs) -> LocatedA (Pat GhcPs)
+fromPParen (L _ (ParPat _ x)) = fromPParen x
+fromPParen x = x
 
-class View' a b where
-  view' :: a -> b
+class View a b where
+  view :: a -> b
 
-data Var_'  = NoVar_' | Var_' String deriving Eq
-data PVar_' = NoPVar_' | PVar_' String
-data PApp_' = NoPApp_' | PApp_' String [Pat GhcPs]
-data App2'  = NoApp2'  | App2' (LHsExpr GhcPs) (LHsExpr GhcPs) (LHsExpr GhcPs)
-data LamConst1' = NoLamConst1' | LamConst1' (LHsExpr GhcPs)
+data RdrName_ = NoRdrName_ | RdrName_ (LocatedN RdrName)
+data Var_  = NoVar_ | Var_ String deriving Eq
+data PVar_ = NoPVar_ | PVar_ String
+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' -> (LL _ (HsLam _ (MG _ (L _ [LL _ (Match _ LambdaExpr [LL _ WildPat {}]
-    (GRHSs _ [LL _ (GRHS _ [] x)] (LL _ (EmptyLocalBinds _))))]) FromSource)))) = LamConst1' x
-  view' _ = NoLamConst1'
+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) Var_' where
-    view' (fromParen' -> (LL _ (HsVar _ (LL _ (Unqual x))))) = Var_' $ occNameString x
-    view' _ = NoVar_'
+instance View (LocatedA (HsExpr GhcPs)) RdrName_ where
+    view (fromParen -> (L _ (HsVar _ name))) = RdrName_ name
+    view _ = NoRdrName_
 
-instance View' (LHsExpr GhcPs) App2' where
-  view' (fromParen' -> LL _ (OpApp _ lhs op rhs)) = App2' op lhs rhs
-  view' (fromParen' -> LL _ (HsApp _ (LL _ (HsApp _ f x)) y)) = App2' f x y
-  view' _ = NoApp2'
+instance View (LocatedA (HsExpr GhcPs)) Var_ where
+    view (view -> RdrName_ name) = Var_ (rdrNameStr name)
+    view _ = NoVar_
 
-instance View' (Pat GhcPs) PVar_' where
-  view' (fromPParen' -> LL _ (VarPat _ (L _ x))) = PVar_' $ occNameString (rdrNameOcc x)
-  view' _ = NoPVar_'
+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' (Pat GhcPs) PApp_' where
-  view' (fromPParen' -> LL _ (ConPatIn (L _ x) (PrefixCon args))) =
-    PApp_' (occNameString . rdrNameOcc $ x) args
-  view' (fromPParen' -> LL _ (ConPatIn (L _ x) (InfixCon lhs rhs))) =
-    PApp_' (occNameString . rdrNameOcc $ x) [lhs, rhs]
-  view' _ = NoPApp_'
+instance View (LocatedA (Pat GhcPs)) PVar_ where
+  view (fromPParen -> L _ (VarPat _ (L _ x))) = PVar_ $ occNameStr x
+  view _ = NoPVar_
+
+instance View (LocatedA (Pat GhcPs)) PApp_ where
+  view (fromPParen -> L _ (ConPat _ (L _ x) (PrefixCon _ args))) =
+    PApp_ (occNameStr x) args
+  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 :: [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/Grep.hs b/src/Grep.hs
deleted file mode 100644
--- a/src/Grep.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-
-module Grep(runGrep) where
-
-import Hint.All
-import Apply
-import Config.Type
-import HSE.All
-import Control.Monad
-import Data.List
-import Util
-import Idea
-
-import qualified HsSyn as GHC
-import qualified BasicTypes as GHC
-import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
-import SrcLoc as GHC hiding (mkSrcSpan)
-
-runGrep :: String -> ParseFlags -> [FilePath] -> IO ()
-runGrep patt flags files = do
-    exp <- case parseExp patt of
-        ParseOk x -> return x
-        ParseFailed sl msg ->
-            exitMessage $ (if "Parse error" `isPrefixOf` msg then msg else "Parse error in pattern: " ++ msg) ++ "\n" ++
-                          patt ++ "\n" ++
-                          replicate (srcColumn sl - 1) ' ' ++ "^"
-    let scope = scopeCreate $ Module an Nothing [] [] []
-    let unit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExt [] GHC.Boxed
-    let rule = hintRules [HintRule Suggestion "grep" scope exp (Tuple an Boxed []) Nothing []
-                         -- Todo : Replace these with "proper" GHC expressions.
-                          (extendInstances mempty) (extendInstances unit) (extendInstances unit) Nothing]
-    forM_ files $ \file -> do
-        res <- parseModuleEx flags file Nothing
-        case res of
-            Left (ParseError sl msg ctxt) ->
-                print $ rawIdeaN Error (if "Parse error" `isPrefixOf` msg then msg else "Parse error: " ++ msg) (mkSrcSpan sl sl) ctxt Nothing []
-            Right m ->
-                forM_ (applyHints [] rule [m]) $ \i ->
-                    print i{ideaHint="", ideaTo=Nothing}
diff --git a/src/HLint.hs b/src/HLint.hs
--- a/src/HLint.hs
+++ b/src/HLint.hs
@@ -1,43 +1,44 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE RecordWildCards #-}
-{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
 
 module HLint(hlint, readAllSettings) where
 
 import Control.Applicative
 import Control.Monad.Extra
-import Control.Exception
+import Control.Exception.Extra
 import Control.Concurrent.Extra
 import System.Console.CmdArgs.Verbosity
+import GHC.Util.DynFlags
 import Data.List.Extra
 import GHC.Conc
+import System.Directory
 import System.Exit
 import System.IO.Extra
 import System.Time.Extra
 import Data.Tuple.Extra
+import Data.Bifunctor (bimap)
 import Prelude
 
-import Data.Version.Extra
-import System.Process.Extra
-import Data.Maybe
-import System.Directory
-
 import CmdLine
 import Config.Read
 import Config.Type
 import Config.Compute
 import Report
+import Summary
 import Idea
 import Apply
 import Test.All
 import Hint.All
-import Grep
+import Refact
 import Timing
-import Test.Proof
 import Parallel
-import HSE.All
+import GHC.All
 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.
@@ -53,61 +54,26 @@
 --   on your server with untrusted input.
 hlint :: [String] -> IO [Idea]
 hlint args = do
+    startTimings
     cmd <- getCmd args
-    case cmd of
-        CmdMain{} -> do
-            startTimings
-            (time, xs) <- duration $ hlintMain args cmd
-            when (cmdTiming cmd) $ do
-                printTimings
-                putStrLn $ "Took " ++ showDuration time
-            return $ if cmdNoExitCode cmd then [] else xs
-        CmdGrep{} -> hlintGrep cmd >> return []
-        CmdHSE{}  -> hlintHSE  cmd >> return []
-        CmdTest{} -> hlintTest cmd >> return []
-
-hlintHSE :: Cmd -> IO ()
-hlintHSE c@CmdHSE{..} = do
-    v <- getVerbosity
-    forM_ cmdFiles $ \x -> do
-        putStrLn $ "Parse result of " ++ x ++ ":"
-        let (lang,exts) = cmdExtensions c
-        -- We deliberately don't use HSE.All here to avoid any bugs in HLint
-        -- polluting our bug reports (which is the main use of HSE)
-        res <- parseFileWithMode defaultParseMode{baseLanguage=lang, extensions=exts} x
-        case res of
-            x@ParseFailed{} -> print x
-            ParseOk m -> case v of
-                Loud -> print m
-                Quiet -> print $ prettyPrint m
-                _ -> print $ void m
-        putStrLn ""
+    timedIO "Initialise" "global flags" initGlobalDynFlags
+    if cmdTest cmd then
+        hlintTest cmd >> pure []
+     else do
+        (time, xs) <- duration $ hlintMain args cmd
+        when (cmdTiming cmd) $ do
+            printTimings
+            putStrLn $ "Took " ++ showDuration time
+        pure $ if cmdNoExitCode cmd then [] else xs
 
 hlintTest :: Cmd -> IO ()
-hlintTest cmd@CmdTest{..} =
-    if not $ null cmdProof then do
-        files <- cmdHintFiles cmd
-        s <- readFilesConfig files
-        let reps = if cmdReports == ["report.html"] then ["report.txt"] else cmdReports
-        mapM_ (proof reps s) cmdProof
-     else do
-        failed <- test cmd (\args -> do errs <- hlint args; unless (null errs) $ exitWith $ ExitFailure 1) cmdDataDir cmdGivenHints
-        when (failed > 0) exitFailure
+hlintTest cmd@CmdMain{..} = do
+    failed <- test cmd (\args -> do errs <- hlint args; unless (null errs) $ exitWith $ ExitFailure 1) cmdDataDir cmdGivenHints
+    when (failed > 0) exitFailure
 
 cmdParseFlags :: Cmd -> ParseFlags
 cmdParseFlags cmd = parseFlagsSetLanguage (cmdExtensions cmd) $ defaultParseFlags{cppFlags=cmdCpp cmd}
 
-hlintGrep :: Cmd -> IO ()
-hlintGrep cmd@CmdGrep{..} =
-    if null cmdFiles then
-        exitWithHelp
-     else do
-        files <- concatMapM (resolveFile cmd Nothing) cmdFiles
-        if null files then
-            error "No files found"
-         else
-            runGrep cmdPattern (cmdParseFlags cmd) files
-
 withVerbosity :: Verbosity -> IO a -> IO a
 withVerbosity new act = do
     old <- getVerbosity
@@ -116,18 +82,42 @@
 hlintMain :: [String] -> Cmd -> IO [Idea]
 hlintMain args cmd@CmdMain{..}
     | cmdDefault = do
-        ideas <- if null cmdFiles then return [] else withVerbosity Quiet $
+        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
-        return []
+        pure []
+    | 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 >> return []
+        mapM_ (putStrLn . fst <=< computeSettings (cmdParseFlags cmd)) hints >> pure []
     | null cmdFiles =
         exitWithHelp
     | cmdRefactor =
@@ -142,11 +132,17 @@
 
 resolveFiles :: Cmd -> Maybe FilePath -> IO Cmd
 resolveFiles cmd@CmdMain{..} tmpFile = do
+    -- if the first file is named 'lint' and there is no 'lint' file
+    -- then someone is probably invoking the older hlint multi-mode command
+    -- so skip it
+    cmdFiles <- if not $ ["lint"] `isPrefixOf` cmdFiles then pure cmdFiles else do
+        b <- doesDirectoryExist "lint"
+        pure $ if b then cmdFiles else drop1 cmdFiles
+
     files <- concatMapM (resolveFile cmd tmpFile) cmdFiles
     if null files
         then error "No files found"
         else pure cmd { cmdFiles = files }
-resolveFiles cmd _ = pure cmd
 
 readAllSettings :: [String] -> Cmd -> IO (Cmd, [Setting])
 readAllSettings args1 cmd@CmdMain{..} = do
@@ -154,13 +150,14 @@
     settings1 <-
         readFilesConfig $
         files
-        ++ [("CommandLine.hs",Just x) | x <- cmdWithHints]
         ++ [("CommandLine.yaml",Just (enableGroup x)) | x <- cmdWithGroups]
     let args2 = [x | SettingArgument x <- settings1]
-    cmd@CmdMain{..} <- if null args2 then return cmd else getCmd $ args2 ++ args1 -- command line arguments are passed last
+    cmd@CmdMain{..} <- if null args2 then pure cmd else getCmd $ args2 ++ args1 -- command line arguments are passed last
     settings2 <- concatMapM (fmap snd . computeSettings (cmdParseFlags cmd)) cmdFindHints
-    settings3 <- return [SettingClassify $ Classify Ignore x "" "" | x <- cmdIgnore]
-    return (cmd, settings1 ++ settings2 ++ settings3)
+    let settings3 = [SettingClassify $ Classify Ignore x "" "" | x <- cmdIgnore]
+    cmdThreads <- if cmdThreads == 0 then getNumProcessors else pure cmdThreads
+    cmd <- pure CmdMain {..}
+    pure (cmd, settings1 ++ settings2 ++ settings3)
     where
         enableGroup groupName =
             unlines
@@ -170,16 +167,16 @@
             ]
 
 runHints :: [String] -> [Setting] -> Cmd -> IO [Idea]
-runHints args settings cmd@CmdMain{..} = do
-    j <- if cmdThreads == 0 then getNumProcessors else return cmdThreads
-    withNumCapabilities j $ do
+runHints args settings cmd@CmdMain{..} =
+    withNumCapabilities cmdThreads $ do
         let outStrLn = whenNormal . putStrLn
-        ideas <- getIdeas cmd settings
-        ideas <- return $ 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
@@ -187,24 +184,31 @@
             handleRefactoring ideas cmdFiles cmd
          else do
             usecolour <- cmdUseColour cmd
-            showItem <- if usecolour then showANSI else return show
+            let showItem = if usecolour then showIdeaANSI else show
             mapM_ (outStrLn . showItem) ideas
             handleReporting ideas cmd
-        return ideas
+        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
-    settings <- return $ settings ++ map (Builtin . fst) builtinHints
+    settings <- pure $ settings ++ map (Builtin . fst) builtinHints
     let flags = cmdParseFlags cmd
     ideas <- if cmdCross
         then applyHintFiles flags settings cmdFiles
         else concat <$> parallel cmdThreads [evaluateList =<< applyHintFile flags settings x Nothing | x <- cmdFiles]
-    return $ if not (null cmdOnly)
+    pure $ if not (null cmdOnly)
         then [i | i <- ideas, ideaHint i `elem` cmdOnly]
         else ideas
 
+-- #746: run refactor even if no hint, which ensures consistent output
+-- whether there are hints or not.
 handleRefactoring :: [Idea] -> [String] -> Cmd -> IO ()
-handleRefactoring [] _ _ = pure () -- No refactorings to apply
 handleRefactoring ideas files cmd@CmdMain{..} =
     case cmdFiles of
         [file] -> do
@@ -214,9 +218,9 @@
             let hints =  show $ map (show &&& ideaRefactoring) ideas
             withTempFile $ \f -> do
                 writeFile f hints
-                exitWith =<< runRefactoring path file f cmdRefactorOptions
-        _ -> error "Refactor flag can only be used with an individual file"
-
+                let ParseFlags{enabledExtensions, disabledExtensions} = cmdParseFlags cmd
+                exitWith =<< runRefactoring path file f enabledExtensions disabledExtensions cmdRefactorOptions
+        _ -> errorIO "Refactor flag can only be used with an individual file"
 
 handleReporting :: [Idea] -> Cmd -> IO ()
 handleReporting showideas cmd@CmdMain{..} = do
@@ -225,31 +229,16 @@
         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]
-
-runRefactoring :: FilePath -> FilePath -> FilePath -> String -> IO ExitCode
-runRefactoring rpath fin hints opts =  do
-    let args = [fin, "-v0"] ++ words opts ++ ["--refact-file", hints]
-    (_, _, _, phand) <- createProcess $ proc rpath args
-    try $ hSetBuffering stdin LineBuffering :: IO (Either IOException ())
-    hSetBuffering stdout LineBuffering
-    -- Propagate the exit code from the spawn process
-    waitForProcess phand
+        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")
 
-checkRefactor :: Maybe FilePath -> IO FilePath
-checkRefactor rpath = do
-    let excPath = fromMaybe "refactor" rpath
-    mexc <- findExecutable excPath
-    case mexc of
-        Just exc ->  do
-            ver <- readVersion . tail <$> readProcess exc ["--version"] ""
-            if versionBranch ver >= [0,1,0,0]
-                then return exc
-                else error "Your version of refactor is too old, please upgrade to the latest version"
-        Nothing -> error $ unlines [ "Could not find refactor", "Tried with: " ++ excPath ]
+    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
     evaluate $ length xs
-    return xs
+    pure xs
diff --git a/src/HSE/All.hs b/src/HSE/All.hs
deleted file mode 100644
--- a/src/HSE/All.hs
+++ /dev/null
@@ -1,423 +0,0 @@
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE TupleSections #-}
-
-module HSE.All(
-    module X,
-    CppFlags(..), ParseFlags(..), defaultParseFlags,
-    parseFlagsAddFixities, parseFlagsSetLanguage,
-    ParseError(..), ModuleEx(..),
-    parseModuleEx, ghcComments,
-    freeVars, vars, varss, pvars,
-    ghcSpanToHSE, ghcSrcLocToHSE,
-    parseExpGhcWithMode, parseImportDeclGhcWithMode
-    ) where
-
-import Language.Haskell.Exts.Util hiding (freeVars, Vars(..))
-import qualified Language.Haskell.Exts.Util as X
-import HSE.Util as X
-import HSE.Type as X
-import HSE.Match as X
-import HSE.Scope as X
-import Util
-import Data.Char
-import Data.List.Extra
-import Data.Maybe
-import Timing
-import Language.Preprocessor.Cpphs
-import Data.Either
-import Data.Set (Set)
-import qualified Data.Map as Map
-import qualified Data.Set as Set
-import System.IO.Extra
-import Data.Functor
-import Prelude
-
-import qualified HsSyn
-import qualified FastString
-import qualified SrcLoc as GHC
-import qualified ErrUtils
-import qualified Outputable
-import qualified Lexer as GHC
-import qualified GHC.LanguageExtensions.Type as GHC
-import qualified ApiAnnotation as GHC
-import qualified BasicTypes as GHC
-import qualified DynFlags as GHC
-
-import GHC.Util
-import qualified Language.Haskell.GhclibParserEx.Fixity as GhclibParserEx
-
--- | Convert a GHC source loc into an HSE equivalent.
-ghcSrcLocToHSE :: GHC.SrcLoc -> SrcLoc
-ghcSrcLocToHSE (GHC.RealSrcLoc l) =
-  SrcLoc {
-      srcFilename = FastString.unpackFS (GHC.srcLocFile l)
-    , srcLine = GHC.srcLocLine l
-    , srcColumn = GHC.srcLocCol l
-    }
-ghcSrcLocToHSE (GHC.UnhelpfulLoc _) = noLoc
-
--- | Convert a GHC source span into an HSE equivalent.
-ghcSpanToHSE :: GHC.SrcSpan -> SrcSpan
-ghcSpanToHSE (GHC.RealSrcSpan s) =
-  SrcSpan {
-      srcSpanFilename = FastString.unpackFS (GHC.srcSpanFile s)
-    , srcSpanStartLine = GHC.srcSpanStartLine s
-    , srcSpanStartColumn = GHC.srcSpanStartCol s
-    , srcSpanEndLine = GHC.srcSpanEndLine s
-    , srcSpanEndColumn = GHC.srcSpanEndCol s
-    }
-ghcSpanToHSE (GHC.UnhelpfulSpan _) = mkSrcSpan noLoc noLoc
-
-vars :: FreeVars a => a -> [String]
-freeVars :: FreeVars a => a -> Set String
-varss, pvars :: AllVars a => a -> [String]
-vars  = Set.toList . Set.map prettyPrint . X.freeVars
-varss = Set.toList . Set.map prettyPrint . X.free . X.allVars
-pvars = Set.toList . Set.map prettyPrint . X.bound . X.allVars
-freeVars = Set.map prettyPrint . X.freeVars
-
--- | What C pre processor should be used.
-data CppFlags
-    = NoCpp -- ^ No pre processing is done.
-    | CppSimple -- ^ Lines prefixed with @#@ are stripped.
-    | Cpphs CpphsOptions -- ^ The @cpphs@ library is used.
-
--- | Created with 'defaultParseFlags', used by 'parseModuleEx'.
-data ParseFlags = ParseFlags
-    {cppFlags :: CppFlags -- ^ How the file is preprocessed (defaults to 'NoCpp').
-    ,hseFlags :: ParseMode -- ^ How the file is parsed (defaults to all fixities in the @base@ package and most non-conflicting extensions).
-    }
-
-lensFixities :: [Fixity]
-lensFixities = concat
-    -- List as provided at https://github.com/ndmitchell/hlint/issues/416
-    [infixr_ 4 ["%%@~","<%@~","%%~","<+~","<*~","<-~","<//~","<^~","<^^~","<**~"]
-    ,infix_ 4 ["%%@=","<%@=","%%=","<+=","<*=","<-=","<//=","<^=","<^^=","<**="]
-    ,infixr_ 2 ["<<~"]
-    ,infixr_ 9 ["#."]
-    ,infixl_ 8 [".#"]
-    ,infixr_ 8 ["^!","^@!"]
-    ,infixl_ 1 ["&","<&>","??"]
-    ,infixl_ 8 ["^.","^@."]
-    ,infixr_ 9 ["<.>","<.",".>"]
-    ,infixr_ 4 ["%@~",".~","+~","*~","-~","//~","^~","^^~","**~","&&~","<>~","||~","%~"]
-    ,infix_ 4 ["%@=",".=","+=","*=","-=","//=","^=","^^=","**=","&&=","<>=","||=","%="]
-    ,infixr_ 2 ["<~"]
-    ,infixr_ 2 ["`zoom`","`magnify`"]
-    ,infixl_ 8 ["^..","^?","^?!","^@..","^@?","^@?!"]
-    ,infixl_ 8 ["^#"]
-    ,infixr_ 4 ["<#~","#~","#%~","<#%~","#%%~"]
-    ,infix_ 4 ["<#=","#=","#%=","<#%=","#%%="]
-    ,infixl_ 9 [":>"]
-    ,infixr_ 4 ["</>~","<</>~","<.>~","<<.>~"]
-    ,infix_ 4 ["</>=","<</>=","<.>=","<<.>="]
-    ,infixr_ 4 [".|.~",".&.~","<.|.~","<.&.~"]
-    ,infix_ 4 [".|.=",".&.=","<.|.=","<.&.="]
-    ]
-
-otherFixities :: [Fixity]
-otherFixities = concat
-    -- hspec
-    [infix_ 1 ["`shouldBe`","`shouldSatisfy`","`shouldStartWith`","`shouldEndWith`","`shouldContain`","`shouldMatchList`"
-              ,"`shouldReturn`","`shouldNotBe`","`shouldNotSatisfy`","`shouldNotContain`","`shouldNotReturn`","`shouldThrow`"]
-    -- quickcheck
-    ,infixr_ 0 ["==>"]
-    ,infix_ 4 ["==="]
-    -- esqueleto
-    ,infix_ 4 ["==."]
-    -- lattices
-    ,infixr_ 5 ["\\/"] -- \/
-    ,infixr_ 6 ["/\\"] -- /\
-    ]
-
--- Fixites from the `base` package which are currently
--- missing from `haskell-src-exts`'s baseFixities.
--- see https://github.com/haskell-suite/haskell-src-exts/pull/400
-baseNotYetInHSE :: [Fixity]
-baseNotYetInHSE = concat
-    [infixr_ 9 ["`Compose`"]
-    ,infixr_ 6 ["<>"]
-    ,infixr_ 5 ["<|"]
-    ,infixl_ 4 ["<$!>","<$","$>"]
-    ,infix_ 4 [":~:", ":~~:"]
-    ]
-
-customFixities :: [Fixity]
-customFixities =
-    infixl_ 1 ["`on`"]
-        -- see https://github.com/ndmitchell/hlint/issues/425
-        -- otherwise GTK apps using `on` at a different fixity have spurious warnings
-
--- | Default value for 'ParseFlags'.
-defaultParseFlags :: ParseFlags
-defaultParseFlags = ParseFlags NoCpp defaultParseMode
-    {fixities = Just $ customFixities ++ baseFixities ++ baseNotYetInHSE ++ lensFixities ++ otherFixities
-    ,ignoreLinePragmas = False
-    ,ignoreFunctionArity = True
-    ,extensions = parseExtensions}
-
-parseFlagsNoLocations :: ParseFlags -> ParseFlags
-parseFlagsNoLocations x = x{cppFlags = case cppFlags x of Cpphs y -> Cpphs $ f y; y -> y}
-    where f x = x{boolopts = (boolopts x){locations=False}}
-
--- | Given some fixities, add them to the existing fixities in 'ParseFlags'.
-parseFlagsAddFixities :: [Fixity] -> ParseFlags -> ParseFlags
-parseFlagsAddFixities fx x = x{hseFlags=hse{fixities = Just $ fx ++ fromMaybe [] (fixities hse)}}
-    where hse = hseFlags x
-
-parseFlagsSetLanguage :: (Language, [Extension]) -> ParseFlags -> ParseFlags
-parseFlagsSetLanguage (l, es) x = x{hseFlags=(hseFlags x){baseLanguage = l, extensions = es}}
-
-
-runCpp :: CppFlags -> FilePath -> String -> IO String
-runCpp NoCpp _ x = return x
-runCpp CppSimple _ x = return $ unlines [if "#" `isPrefixOf` trimStart x then "" else x | x <- lines x]
-runCpp (Cpphs o) file x = dropLine <$> runCpphs o file x
-    where
-        -- LINE pragmas always inserted when locations=True
-        dropLine (line1 -> (a,b)) | "{-# LINE " `isPrefixOf` a = b
-        dropLine x = x
-
----------------------------------------------------------------------
--- PARSING
-
--- | A parse error.
-data ParseError = ParseError
-    { parseErrorLocation :: SrcLoc -- ^ Location of the error.
-    , parseErrorMessage :: String  -- ^ Message about the cause of the error.
-    , parseErrorContents :: String -- ^ Snippet of several lines (typically 5) including a @>@ character pointing at the faulty line.
-    }
-
--- | Result of 'parseModuleEx', representing a parsed module.
-data ModuleEx = ModuleEx {
-    hseModule :: Module SrcSpanInfo
-  , hseComments :: [Comment]
-  , ghcModule :: GHC.Located (HsSyn.HsModule HsSyn.GhcPs)
-  , ghcAnnotations :: GHC.ApiAnns
-}
-
--- | Extract a list of all of a parsed module's comments.
-ghcComments :: ModuleEx -> [GHC.Located GHC.AnnotationComment]
-ghcComments m = concat (Map.elems $ snd (ghcAnnotations m))
-
--- | Utility called from 'parseModuleEx' and 'hseFailOpParseModuleEx'.
-mkMode :: ParseFlags -> String -> ParseMode
-mkMode flags file = (hseFlags flags){ parseFilename = file,fixities = Nothing }
-
--- | Error handler dispatcher. Invoked when HSE parsing has failed.
-failOpParseModuleEx :: String
-                    -> ParseFlags
-                    -> FilePath
-                    -> String
-                    -> SrcLoc
-                    -> String
-                    -> Maybe (GHC.SrcSpan, ErrUtils.MsgDoc)
-                    -> IO (Either ParseError ModuleEx)
-failOpParseModuleEx ppstr flags file str sl msg ghc =
-   case ghc of
-     Just err ->
-       -- GHC error info is available (assumed to have come from a
-       -- 'PFailed'). We prefer to construct a 'ParseError' value
-       -- using that.
-       ghcFailOpParseModuleEx ppstr file str err
-     Nothing ->
-       -- No GHC error info provided. This is the traditional approach
-       -- to handling errors.
-       hseFailOpParseModuleEx ppstr flags file str sl msg
-
--- | An error handler of last resort. This is invoked when HSE parsing
--- has failed but apparently GHC has not!
-hseFailOpParseModuleEx :: String
-                       -> ParseFlags
-                       -> FilePath
-                       -> String
-                       -> SrcLoc
-                       -> String
-                       -> IO (Either ParseError ModuleEx)
-hseFailOpParseModuleEx ppstr flags file str sl msg = do
-    flags <- return $ parseFlagsNoLocations flags
-    ppstr2 <- runCpp (cppFlags flags) file str
-    let pe = case parseFileContentsWithMode (mkMode flags file) ppstr2 of
-               ParseFailed sl2 _ -> context (srcLine sl2) ppstr2
-               _ -> context (srcLine sl) ppstr
-    return $ Left $ ParseError sl msg pe
-
--- | The error handler invoked when GHC parsing has failed.
-ghcFailOpParseModuleEx :: String
-                       -> FilePath
-                       -> String
-                       -> (GHC.SrcSpan, ErrUtils.MsgDoc)
-                       -> IO (Either ParseError ModuleEx)
-ghcFailOpParseModuleEx ppstr file str (loc, err) = do
-   let sl =
-         case loc of
-           GHC.RealSrcSpan r ->
-             SrcLoc { srcFilename = FastString.unpackFS (GHC.srcSpanFile r)
-                     , srcLine = GHC.srcSpanStartLine r
-                     , srcColumn = GHC.srcSpanStartCol r }
-           GHC.UnhelpfulSpan _ ->
-             SrcLoc { srcFilename = file
-                     , srcLine = 1 :: Int
-                     , srcColumn = 1 :: Int }
-       pe = context (srcLine sl) ppstr
-       msg = Outputable.showSDoc baseDynFlags $
-               ErrUtils.pprLocErrMsg (ErrUtils.mkPlainErrMsg baseDynFlags loc err)
-   return $ Left $ ParseError sl msg pe
-
--- A hacky function to get fixities from HSE parse flags suitable for
--- use by our own 'GHC.Util.Refact.Fixity' module.
-ghcFixitiesFromParseMode :: ParseMode -> [(String, GHC.Fixity)]
-ghcFixitiesFromParseMode ParseMode {fixities=Just fixities} =
-  concatMap convert fixities
-  where
-    convert (Fixity (AssocNone _) fix name) = infix_' fix [qNameToStr name]
-    convert (Fixity (AssocLeft _) fix name) = infixl_' fix [qNameToStr name]
-    convert (Fixity (AssocRight _) fix name) = infixr_' fix [qNameToStr name]
-
-    infixr_', infixl_', infix_' :: Int -> [String] -> [(String,GHC.Fixity)]
-    infixr_' = fixity' GHC.InfixR
-    infixl_' = fixity' GHC.InfixL
-    infix_'  = fixity' GHC.InfixN
-
-    fixity' :: GHC.FixityDirection -> Int -> [String] -> [(String, GHC.Fixity)]
-    fixity' a p = map (,GHC.Fixity (GHC.SourceText "") p a)
-
-    qNameToStr :: QName () -> String
-    qNameToStr (Special _ Cons{}) = ":"
-    qNameToStr (Special _ UnitCon{}) = "()"
-    qNameToStr (UnQual _ (X.Ident _ x)) = x
-    qNameToStr (UnQual _ (Symbol _ x)) = x
-    qNameToStr _ = ""
-ghcFixitiesFromParseMode _ = []
-
--- GHC enabled/disabled extensions given an HSE parse mode.
-ghcExtensionsFromParseMode :: ParseMode
-                           -> ([GHC.Extension], [GHC.Extension])
-ghcExtensionsFromParseMode ParseMode {extensions=exts}=
-   partitionEithers $ mapMaybe toEither exts
-   where
-     toEither ke = case ke of
-       EnableExtension e  -> Left  <$> readExtension (show e)
-       DisableExtension e -> Right <$> readExtension (show e)
-       UnknownExtension ('N':'o':e) -> Right <$> readExtension e
-       UnknownExtension e -> Left <$> readExtension e
-
--- GHC extensions to enable/disable given HSE parse flags.
-ghcExtensionsFromParseFlags :: ParseFlags
-                             -> ([GHC.Extension], [GHC.Extension])
-ghcExtensionsFromParseFlags ParseFlags {hseFlags=mode} = ghcExtensionsFromParseMode mode
-
--- GHC fixities given HSE parse flags.
-ghcFixitiesFromParseFlags :: ParseFlags -> [(String, GHC.Fixity)]
-ghcFixitiesFromParseFlags ParseFlags {hseFlags=mode} = ghcFixitiesFromParseMode mode
-
--- These next two functions get called frorm 'Config/Yaml.hs' for user
--- defined hint rules.
-
-parseExpGhcWithMode :: ParseMode -> String -> GHC.ParseResult (HsSyn.LHsExpr HsSyn.GhcPs)
-parseExpGhcWithMode parseMode s =
-  let (enable, disable) = ghcExtensionsFromParseMode parseMode
-      flags = foldl' GHC.xopt_unset (foldl' GHC.xopt_set baseDynFlags enable) disable
-      fixities = ghcFixitiesFromParseMode parseMode
-  in case parseExpGhcLib s flags of
-    GHC.POk pst a -> GHC.POk pst (GhclibParserEx.applyFixities fixities a)
-    f@GHC.PFailed{} -> f
-
-parseImportDeclGhcWithMode :: ParseMode -> String -> GHC.ParseResult (HsSyn.LImportDecl HsSyn.GhcPs)
-parseImportDeclGhcWithMode parseMode s =
-  let (enable, disable) = ghcExtensionsFromParseMode parseMode
-      flags = foldl' GHC.xopt_unset (foldl' GHC.xopt_set baseDynFlags enable) disable
-  in parseImportGhcLib s flags
-
--- | 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
--- 'defaultParseFlags'), the filename, and optionally the contents of
--- that file. This version uses both hs-src-exts AND ghc-lib.
-parseModuleEx :: ParseFlags -> FilePath -> Maybe String -> IO (Either ParseError ModuleEx)
-parseModuleEx flags file str = timedIO "Parse" file $ do
-        str <- case str of
-            Just x -> return x
-            Nothing | file == "-" -> getContentsUTF8
-                    | otherwise -> readFileUTF8' file
-        str <- return $ fromMaybe str $ stripPrefix "\65279" str -- remove the BOM if it exists, see #130
-        ppstr <- runCpp (cppFlags flags) file str
-        let enableDisableExts = ghcExtensionsFromParseFlags flags
-            fixities = ghcFixitiesFromParseFlags flags -- Note : Fixities are coming from HSE parse flags.
-        dynFlags <- parsePragmasIntoDynFlags baseDynFlags enableDisableExts file ppstr
-        case dynFlags of
-          Right ghcFlags ->
-            case (parseFileContentsWithComments (mkMode flags file) ppstr, parseFileGhcLib file ppstr ghcFlags) of
-                (ParseOk (x, cs), GHC.POk pst a) ->
-                    let anns =
-                          ( Map.fromListWith (++) $ GHC.annotations pst
-                          , Map.fromList ((GHC.noSrcSpan, GHC.comment_q pst) : GHC.annotations_comments pst)
-                          ) in
-                    let a' = GhclibParserEx.applyFixities fixities a in
-                    return $ Right (ModuleEx (applyFixity fixity x) cs a' anns)
-                -- Parse error if GHC parsing fails (see
-                -- https://github.com/ndmitchell/hlint/issues/645).
-                (ParseOk _,  GHC.PFailed _ loc err) ->
-                    ghcFailOpParseModuleEx ppstr file str (loc, err)
-                (ParseFailed sl msg, pfailed) ->
-                    failOpParseModuleEx ppstr flags file str sl msg $ fromPFailed pfailed
-          Left msg -> do
-            -- Parsing GHC flags from dynamic pragmas in the source
-            -- has failed. When this happens, it's reported by
-            -- exception. It's impossible or at least fiddly getting a
-            -- location so we skip that for now. Synthesize a parse
-            -- error.
-            let loc = SrcLoc file (1 :: Int) (1 :: Int)
-            return $ Left (ParseError loc msg (context (srcLine loc) ppstr))
-
-    where
-        fromPFailed (GHC.PFailed _ loc err) = Just (loc, err)
-        fromPFailed _ = Nothing
-
-        fixity = fromMaybe [] $ fixities $ hseFlags flags
-
--- | Given a line number, and some source code, put bird ticks around the appropriate bit.
-context :: Int -> String -> String
-context lineNo src =
-    unlines $ dropWhileEnd (all isSpace) $ dropWhile (all isSpace) $
-    zipWith (++) ticks $ take 5 $ drop (lineNo - 3) $ lines src ++ ["","","","",""]
-    where ticks = ["  ","  ","> ","  ","  "]
-
-
----------------------------------------------------------------------
--- FIXITIES
-
--- resolve fixities later, so we don't ever get uncatchable ambiguity errors
--- if there are fixity errors, try the cheapFixities (which never fails)
-applyFixity :: [Fixity] -> Module_ -> Module_
-applyFixity base modu = descendBi f modu
-    where
-        f x = fromMaybe (cheapFixities fixs x) $ applyFixities fixs x :: Decl_
-        fixs = concatMap getFixity (moduleDecls modu) ++ base
-
-
--- Apply fixities, but ignoring any ambiguous fixity errors and skipping qualified names,
--- local infix declarations etc. Only use as a backup, if HSE gives an error.
---
--- Inspired by the code at:
--- http://hackage.haskell.org/trac/haskell-prime/attachment/wiki/FixityResolution/resolve.hs
-cheapFixities :: [Fixity] -> Decl_ -> Decl_
-cheapFixities fixs = descendBi (transform f)
-    where
-        ask = askFixity fixs
-
-        f o@(InfixApp s1 (InfixApp s2 x op1 y) op2 z)
-                | p1 == p2 && (a1 /= a2 || isAssocNone a1) = o -- Ambiguous infix expression!
-                | p1 > p2 || p1 == p2 && (isAssocLeft a1 || isAssocNone a2) = o
-                | otherwise = InfixApp s1 x op1 (f $ InfixApp s1 y op2 z)
-            where
-                (a1,p1) = ask op1
-                (a2,p2) = ask op2
-        f x = x
-
-
-askFixity :: [Fixity] -> QOp S -> (Assoc (), Int)
-askFixity xs = \k -> Map.findWithDefault (AssocLeft (), 9) (fromNamed k) mp
-    where
-        mp = Map.fromList [(s,(a,p)) | Fixity a p x <- xs, let s = fromNamed $ fmap (const an) x, s /= ""]
diff --git a/src/HSE/Match.hs b/src/HSE/Match.hs
deleted file mode 100644
--- a/src/HSE/Match.hs
+++ /dev/null
@@ -1,142 +0,0 @@
-{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances #-}
-
-module HSE.Match(
-    View(..), Named(..),
-    (~=), isSym,
-    App2(App2), PVar_(PVar_), Var_(Var_)
-    ) where
-
-import Data.Char
-import HSE.Type
-import HSE.Util
-
-
-class View a b where
-    view :: a -> b
-
-
-data App2 = NoApp2 | App2 Exp_ Exp_ Exp_ deriving Show
-
-instance View Exp_ App2 where
-    view (fromParen -> InfixApp _ lhs op rhs) = App2 (opExp op) lhs rhs
-    view (fromParen -> App _ (fromParen -> App _ f x) y) = App2 f x y
-    view _ = NoApp2
-
-
-data App1 = NoApp1 | App1 Exp_ Exp_ deriving Show
-
-instance View Exp_ App1 where
-    view (fromParen -> App _ f x) = App1 f x
-    view _ = NoApp1
-
-data PVar_ = NoPVar_ | PVar_ String
-
-instance View Pat_ PVar_ where
-    view (fromPParen -> PVar _ x) = PVar_ $ fromNamed x
-    view _ = NoPVar_
-
-data Var_ = NoVar_ | Var_ String deriving Eq
-
-instance View Exp_ Var_ where
-    view (fromParen -> Var _ (UnQual _ x)) = Var_ $ fromNamed x
-    view _ = NoVar_
-
-
-(~=) :: Named a => a -> String -> Bool
-(~=) = (==) . fromNamed
-
-
--- | fromNamed will return \"\" when it cannot be represented
---   toNamed may crash on \"\"
-class Named a where
-    toNamed :: String -> a
-    fromNamed :: a -> String
-
-
-isCtor (x:_) = isUpper x || x == ':'
-isCtor _ = False
-
-isSym (x:_) = not $ isAlpha x || x `elem` "_'"
-isSym _ = False
-
-
-instance Named (Exp S) where
-    fromNamed (Var _ x) = fromNamed x
-    fromNamed (Con _ x) = fromNamed x
-    fromNamed (List _ []) = "[]"
-    fromNamed _ = ""
-
-    toNamed "[]" = List an []
-    toNamed x | isCtor x = Con an $ toNamed x
-              | otherwise = Var an $ toNamed x
-
-instance Named (QName S) where
-    fromNamed (Special _ Cons{}) = ":"
-    fromNamed (Special _ UnitCon{}) = "()"
-    fromNamed (UnQual _ x) = fromNamed x
-    fromNamed _ = ""
-
-    toNamed ":" = Special an $ Cons an
-    toNamed x = UnQual an $ toNamed x
-
-instance Named (Name S) where
-    fromNamed (Ident _ x) = x
-    fromNamed (Symbol _ x) = x
-
-    toNamed x | isSym x = Symbol an x
-              | otherwise = Ident an x
-
-instance Named (ModuleName S) where
-    fromNamed (ModuleName _ x) = x
-    toNamed = ModuleName an
-
-
-instance Named (Pat S) where
-    fromNamed (PVar _ x) = fromNamed x
-    fromNamed (PApp _ x []) = fromNamed x
-    fromNamed (PList _ []) = "[]"
-    fromNamed _ = ""
-
-    toNamed x | isCtor x = PApp an (toNamed x) []
-              | otherwise = PVar an $ toNamed x
-
-
-instance Named (TyVarBind S) where
-    fromNamed (KindedVar _ x _) = fromNamed x
-    fromNamed (UnkindedVar _ x) = fromNamed x
-    toNamed x = UnkindedVar an (toNamed x)
-
-
-instance Named (QOp S) where
-    fromNamed (QVarOp _ x) = fromNamed x
-    fromNamed (QConOp _ x) = fromNamed x
-    toNamed x | isCtor x = QConOp an $ toNamed x
-              | otherwise = QVarOp an $ toNamed x
-
-instance Named (Match S) where
-    fromNamed (Match _ x _ _ _) = fromNamed x
-    fromNamed (InfixMatch _ _ x _ _ _) = fromNamed x
-    toNamed = error "No toNamed for Match"
-
-instance Named (DeclHead S) where
-    fromNamed (DHead _ x) = fromNamed x
-    fromNamed (DHInfix _ _ x) = fromNamed x
-    fromNamed (DHParen _ x) = fromNamed x
-    fromNamed (DHApp _ x _) = fromNamed x
-    toNamed = error "No toNamed for DeclHead"
-
-instance Named (Decl S) where
-    fromNamed (TypeDecl _ name _) = fromNamed name
-    fromNamed (DataDecl _ _ _ name _ _) = fromNamed name
-    fromNamed (GDataDecl _ _ _ name _ _ _) = fromNamed name
-    fromNamed (TypeFamDecl _ name _ _) = fromNamed name
-    fromNamed (DataFamDecl _ _ name _) = fromNamed name
-    fromNamed (ClassDecl _ _ name _ _) = fromNamed name
-    fromNamed (PatBind _ (PVar _ name) _ _) = fromNamed name
-    fromNamed (FunBind _ (name:_)) = fromNamed name
-    fromNamed (ForImp _ _ _ _ name _) = fromNamed name
-    fromNamed (ForExp _ _ _ name _) = fromNamed name
-    fromNamed (TypeSig _ (name:_) _) = fromNamed name
-    fromNamed _ = ""
-
-    toNamed = error "No toNamed for Decl"
diff --git a/src/HSE/Scope.hs b/src/HSE/Scope.hs
deleted file mode 100644
--- a/src/HSE/Scope.hs
+++ /dev/null
@@ -1,48 +0,0 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module HSE.Scope(
-    Scope, scopeCreate, scopeImports
-    ) where
-
-import Data.Semigroup
-import HSE.Type
-import HSE.Util
-import Data.List
-import Data.Maybe
-import Prelude
-
-{-
-the hint file can do:
-
-import Prelude (filter)
-import Data.List (filter)
-import List (filter)
-
-then filter on it's own will get expanded to all of them
-
-import Data.List
-import List as Data.List
-
-
-if Data.List.head x ==> x, then that might match List too
--}
-
-
--- | Data type representing the modules in scope within a module.
---   Created with 'scopeCreate' and queried with 'scopeMatch' and 'scopeMove'.
---   Note that the 'mempty' 'Scope' is not equivalent to 'scopeCreate' on an empty module,
---   due to the implicit import of 'Prelude'.
-newtype Scope = Scope [ImportDecl S]
-             deriving (Show, Monoid, Semigroup)
-
--- | Create a 'Scope' value from a module, based on the modules imports.
-scopeCreate :: Module SrcSpanInfo -> Scope
-scopeCreate xs = Scope $ [prelude | not $ any isPrelude res] ++ res
-    where
-        res = [x | x <- moduleImports xs, importPkg x /= Just "hint"]
-        prelude = ImportDecl an (ModuleName an "Prelude") False False False Nothing Nothing Nothing
-        isPrelude x = fromModuleName (importModule x) == "Prelude"
-
-
-scopeImports :: Scope -> [ImportDecl S]
-scopeImports (Scope x) = x
diff --git a/src/HSE/Type.hs b/src/HSE/Type.hs
deleted file mode 100644
--- a/src/HSE/Type.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-
-module HSE.Type(
-    S,
-    Module_, Decl_, Exp_, Pat_,
-    module HSE,
-    module Uniplate
-    ) where
-
--- Almost all from the Annotated module, but the fixity resolution from Annotated
--- uses the unannotated Assoc enumeration, so export that instead
-import Language.Haskell.Exts as HSE hiding (parse, loc, paren)
-import Data.Generics.Uniplate.Data as Uniplate
-
-type S = SrcSpanInfo
-type Module_ = Module S
-type Decl_ = Decl S
-type Exp_ = Exp S
-type Pat_ = Pat S
diff --git a/src/HSE/Util.hs b/src/HSE/Util.hs
deleted file mode 100644
--- a/src/HSE/Util.hs
+++ /dev/null
@@ -1,420 +0,0 @@
-{-# LANGUAGE FlexibleContexts, TupleSections #-}
-
-module HSE.Util(module HSE.Util, def) where
-
-import Control.Monad
-import Data.Default
-import Data.Tuple.Extra
-import Data.List
-import Language.Haskell.Exts.Util
-import Control.Monad.Trans.State
-import qualified Data.Map as Map
-import Data.Maybe
-import Data.Data hiding (Fixity)
-import System.FilePath
-import HSE.Type
-import Data.Functor
-import Prelude
-
-
----------------------------------------------------------------------
--- ACCESSOR/TESTER
-
-ellipses :: QName S
-ellipses = UnQual an $ Ident an "..." -- Must be an Ident, not a Symbol
-
-opExp :: QOp S -> Exp_
-opExp (QVarOp s op) = Var s op
-opExp (QConOp s op) = Con s op
-
-expOp :: Exp_ -> Maybe (QOp S)
-expOp (Var s op) = Just $ QVarOp s op
-expOp (Con s op) = Just $ QConOp s op
-expOp _ = Nothing
-
-moduleDecls :: Module_ -> [Decl_]
-moduleDecls (Module _ _ _ _ xs) = xs
-moduleDecls _ = [] -- XmlPage/XmlHybrid
-
-moduleName :: Module_ -> String
-moduleName (Module _ Nothing _ _ _) = "Main"
-moduleName (Module _ (Just (ModuleHead _ (ModuleName _ x) _ _)) _ _ _) = x
-moduleName _ = "" -- XmlPage/XmlHybrid
-
-moduleImports :: Module_ -> [ImportDecl S]
-moduleImports (Module _ _ _ x _) = x
-moduleImports _ = [] -- XmlPage/XmlHybrid
-
-modulePragmas :: Module_ -> [ModulePragma S]
-modulePragmas (Module _ _ x _ _) = x
-modulePragmas _ = [] -- XmlPage/XmlHybrid
-
-moduleExtensions :: Module_ -> [Name S]
-moduleExtensions x = concat [y | LanguagePragma _ y <- modulePragmas x]
-
-fromModuleName :: ModuleName S -> String
-fromModuleName (ModuleName _ x) = x
-
-fromChar :: Exp_ -> Maybe Char
-fromChar (Lit _ (Char _ x _)) = Just x
-fromChar _ = Nothing
-
-fromPChar :: Pat_ -> Maybe Char
-fromPChar (PLit _ _ (Char _ x _)) = Just x
-fromPChar _ = Nothing
-
-fromString :: Exp_ -> Maybe String
-fromString (Lit _ (String _ x _)) = Just x
-fromString _ = Nothing
-
-fromPString :: Pat_ -> Maybe String
-fromPString (PLit _ _ (String _ x _)) =  Just x
-fromPString _ = Nothing
-
-fromParen1 :: Exp_ -> Exp_
-fromParen1 (Paren _ x) = x
-fromParen1 x = x
-
-fromParen :: Exp_ -> Exp_
-fromParen (Paren _ x) = fromParen x
-fromParen x = x
-
-fromPParen :: Pat s -> Pat s
-fromPParen (PParen _ x) = fromPParen x
-fromPParen x = x
-
-fromTyParen :: Type s -> Type s
-fromTyParen (TyParen _ x) = fromTyParen x
-fromTyParen x = x
-
-fromTyBang :: Type s -> Type s
-fromTyBang (TyBang _ _ _ x) = x
-fromTyBang x = x
-
--- is* :: Exp_ -> Bool
--- is* :: Decl_ -> Bool
-isVar Var{} = True; isVar _ = False
-isCon Con{} = True; isCon _ = False
-isApp App{} = True; isApp _ = False
-isInfixApp InfixApp{} = True; isInfixApp _ = False
-isAnyApp x = isApp x || isInfixApp x
-isParen Paren{} = True; isParen _ = False
-isIf If{} = True; isIf _ = False
-isLambda Lambda{} = True; isLambda _ = False
-isMDo MDo{} = True; isMDo _ = False
-isBoxed Boxed{} = True; isBoxed _ = False
-isDerivDecl DerivDecl{} = True; isDerivDecl _ = False
-isPBangPat PBangPat{} = True; isPBangPat _ = False
-isPFieldPun PFieldPun{} = True; isPFieldPun _ = False
-isFieldPun FieldPun{} = True; isFieldPun _ = False
-isPWildCard PWildCard{} = True; isPWildCard _ = False
-isPFieldWildcard PFieldWildcard{} = True; isPFieldWildcard _ = False
-isFieldWildcard FieldWildcard{} = True; isFieldWildcard _ = False
-isPViewPat PViewPat{} = True; isPViewPat _ = False
-isParComp ParComp{} = True; isParComp _ = False
-isTypeApp TypeApp{} = True; isTypeApp _ = False
-isPatTypeSig PatTypeSig{} = True; isPatTypeSig _ = False
-isQuasiQuote QuasiQuote{} = True; isQuasiQuote _ = False
-isTyQuasiQuote TyQuasiQuote{} = True; isTyQuasiQuote _ = False
-isSpliceDecl SpliceDecl{} = True; isSpliceDecl _ = False
-isNewType NewType{} = True; isNewType _ = False
-isRecStmt RecStmt{} = True; isRecStmt _ = False
-isClsDefSig ClsDefSig{} = True; isClsDefSig _ = False
-isTyBang TyBang{} = True; isTyBang _ = False
-isLCase LCase{} = True; isLCase _ = False
-isTupleSection TupleSection{} = True; isTupleSection _ = False
-isString String{} = True; isString _ = False
-isRecUpdate RecUpdate{} = True; isRecUpdate _ = False
-isRecConstr RecConstr{} = True; isRecConstr _ = False
-
-isSection LeftSection{} = True
-isSection RightSection{} = True
-isSection _ = False
-
-isPrimLiteral PrimInt{} = True
-isPrimLiteral PrimWord{} = True
-isPrimLiteral PrimFloat{} = True
-isPrimLiteral PrimDouble{} = True
-isPrimLiteral PrimChar{} = True
-isPrimLiteral PrimString{} = True
-isPrimLiteral _ = False
-
-
-allowRightSection x = x `notElem` ["-","#"]
-allowLeftSection x = x /= "#"
-
-
-unqual :: QName S -> QName S
-unqual (Qual an _ x) = UnQual an x
-unqual x = x
-
-fromQual :: QName a -> Maybe (Name a)
-fromQual (Qual _ _ x) = Just x
-fromQual (UnQual _ x) = Just x
-fromQual _ = Nothing
-
-isSpecial :: QName S -> Bool
-isSpecial Special{} = True; isSpecial _ = False
-
-isDol :: QOp S -> Bool
-isDol (QVarOp _ (UnQual _ (Symbol _ "$"))) = True
-isDol _ = False
-
-isDot :: QOp S -> Bool
-isDot (QVarOp _ (UnQual _ (Symbol _ "."))) = True
-isDot _ = False
-
-isDotApp :: Exp_ -> Bool
-isDotApp (InfixApp _ _ dot _) | isDot dot = True
-isDotApp _ = False
-
-dotApp :: Exp_ -> Exp_ -> Exp_
-dotApp x = InfixApp an x (QVarOp an $ UnQual an $ Symbol an ".")
-
-dotApps :: [Exp_] -> Exp_
-dotApps [] = error "HSE.Util.dotApps, does not work on an empty list"
-dotApps [x] = x
-dotApps (x:xs) = dotApp x (dotApps xs)
-
-isReturn :: Exp_ -> Bool
--- Allow both pure and return, as they have the same semantics
-isReturn (Var _ (UnQual _ (Ident _ x))) = x == "return" || x == "pure"
-isReturn _ = False
-
-isLexeme Var{} = True
-isLexeme Con{} = True
-isLexeme Lit{} = True
-isLexeme _ = False
-
-isAssocLeft AssocLeft{} = True; isAssocLeft _ = False
-isAssocNone AssocNone{} = True; isAssocNone _ = False
-
-isWHNF :: Exp_ -> Bool
-isWHNF Con{} = True
-isWHNF (Lit _ x) = case x of String{} -> False; Int{} -> False; Frac{} -> False; _ -> True
-isWHNF Lambda{} = True
-isWHNF Tuple{} = True
-isWHNF List{} = True
-isWHNF (Paren _ x) = isWHNF x
-isWHNF (ExpTypeSig _ x _) = isWHNF x
--- other (unknown) constructors may have bang patterns in them, so approximate
-isWHNF (App _ c@Con{} _) | prettyPrint c `elem` ["Just","Left","Right"] = True
-isWHNF _ = False
-
-
--- | Like needBracket, but with a special case for a . b . b, which
---   was removed from haskell-src-exts-util-0.2.2
-needBracketOld :: Int -> Exp_ -> Exp_ -> Bool
-needBracketOld i parent child
-    | isDotApp parent, isDotApp child, i == 1 = False
-    | otherwise = needBracket i parent child
-
-transformBracketOld :: (Exp_ -> Maybe Exp_) -> Exp_ -> Exp_
-transformBracketOld op = snd . g
-    where
-        g = f . descendBracketOld g
-        f x = maybe (False,x) (True,) (op x)
-
--- | Descend, and if something changes then add/remove brackets appropriately
-descendBracketOld :: (Exp_ -> (Bool, Exp_)) -> Exp_ -> Exp_
-descendBracketOld op x = descendIndex g x
-    where
-        g i y = if a then f i b else b
-            where (a,b) = op y
-
-        f i (Paren _ y) | not $ needBracketOld i x y = y
-        f i y           | needBracketOld i x y = addParen y
-        f _ y           = y
-
-descendIndex :: Data a => (Int -> a -> a) -> a -> a
-descendIndex f x = flip evalState 0 $ flip descendM x $ \y -> do
-    i <- get
-    modify (+1)
-    return $ f i y
-
-
----------------------------------------------------------------------
--- HSE FUNCTIONS
-
-getEquations :: Decl s -> [Decl s]
-getEquations (FunBind s xs) = map (FunBind s . (:[])) xs
-getEquations x@PatBind{} = [toFunBind x]
-getEquations x = [x]
-
-
-toFunBind :: Decl s -> Decl s
-toFunBind (PatBind s (PVar _ name) bod bind) = FunBind s [Match s name [] bod bind]
-toFunBind x = x
-
-
--- case and if both have branches, nothing else does
-replaceBranches :: Exp s -> ([Exp s], [Exp s] -> Exp s)
-replaceBranches (If s a b c) = ([b,c], \[b,c] -> If s a b c)
-replaceBranches (Case s a bs) = (concatMap f bs, Case s a . g bs)
-    where
-        f (Alt _ _ (UnGuardedRhs _ x) _) = [x]
-        f (Alt _ _ (GuardedRhss _ xs) _) = [x | GuardedRhs _ _ x <- xs]
-        g (Alt s1 a (UnGuardedRhs s2 _) b:rest) (x:xs) = Alt s1 a (UnGuardedRhs s2 x) b : g rest xs
-        g (Alt s1 a (GuardedRhss s2 ns) b:rest) xs =
-                Alt s1 a (GuardedRhss s2 [GuardedRhs a b x | (GuardedRhs a b _,x) <- zip ns as]) b : g rest bs
-            where (as,bs) = splitAt (length ns) xs
-        g [] [] = []
-        g _ _ = error "HSE.Util.replaceBranches: internal invariant failed, lists are of differing lengths"
-replaceBranches x = ([], \[] -> x)
-
-
----------------------------------------------------------------------
--- VECTOR APPLICATION
-
-
-apps :: [Exp_] -> Exp_
-apps = foldl1 (App an)
-
-fromApps :: Exp_ -> [Exp_]
-fromApps = map fst . fromAppsWithLoc
-
-fromAppsWithLoc :: Exp_ -> [(Exp_, S)]
-fromAppsWithLoc (App l x y) = fromAppsWithLoc x ++ [(y, l)]
-fromAppsWithLoc x = [(x, ann x)]
-
-
--- Rule for the Uniplate Apps functions
--- Given (f a) b, consider the children to be: children f ++ [a,b]
-
-childrenApps :: Exp_ -> [Exp_]
-childrenApps (App s x y) = childrenApps x ++ [y]
-childrenApps x = children x
-
-
-descendApps :: (Exp_ -> Exp_) -> Exp_ -> Exp_
-descendApps f (App s x y) = App s (descendApps f x) (f y)
-descendApps f x = descend f x
-
-
-descendAppsM :: Monad m => (Exp_ -> m Exp_) -> Exp_ -> m Exp_
-descendAppsM f (App s x y) = liftM2 (App s) (descendAppsM f x) (f y)
-descendAppsM f x = descendM f x
-
-
-universeApps :: Exp_ -> [Exp_]
-universeApps x = x : concatMap universeApps (childrenApps x)
-
-transformApps :: (Exp_ -> Exp_) -> Exp_ -> Exp_
-transformApps f = f . descendApps (transformApps f)
-
-transformAppsM :: Monad m => (Exp_ -> m Exp_) -> Exp_ -> m Exp_
-transformAppsM f x = f =<< descendAppsM (transformAppsM f) x
-
-
----------------------------------------------------------------------
--- UNIPLATE FUNCTIONS
-
-universeS :: (Data x, Data (f S)) => x -> [f S]
-universeS = universeBi
-
-childrenS :: (Data x, Data (f S)) => x -> [f S]
-childrenS = childrenBi
-
-
--- return the parent along with the child
-universeParentExp :: Data a => a -> [(Maybe (Int, Exp_), Exp_)]
-universeParentExp xs = concat [(Nothing, x) : f x | x <- childrenBi xs]
-    where f p = concat [(Just (i,p), c) : f c | (i,c) <- zip [0..] $ children p]
-
-
----------------------------------------------------------------------
--- SRCLOC FUNCTIONS
-
-showSrcLoc :: SrcLoc -> String
-showSrcLoc (SrcLoc file line col) = take 1 file ++ f (drop 1 file) ++ ":" ++ show line ++ ":" ++ show col
-    where f (x:y:zs) | isPathSeparator x && isPathSeparator y = f $ x:zs
-          f (x:xs) = x : f xs
-          f [] = []
-
-an :: SrcSpanInfo
-an = def
-
-dropAnn :: Functor f => f SrcSpanInfo -> f ()
-dropAnn = void
-
----------------------------------------------------------------------
--- SRCLOC EQUALITY
-
--- enforce all being on S, as otherwise easy to =~= on a Just, and get the wrong functor
-
-x /=~= y = not $ x =~= y
-
-elem_, notElem_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> Bool
-elem_ x = any (x =~=)
-notElem_ x = not . elem_ x
-
-nub_ :: (Annotated f, Eq (f ())) => [f S] -> [f S]
-nub_ = nubBy (=~=)
-
-delete_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> [f S]
-delete_ = deleteBy (=~=)
-
-intersect_ :: (Annotated f, Eq (f ())) => [f S] -> [f S] -> [f S]
-intersect_ = intersectBy (=~=)
-
-eqList, neqList :: (Annotated f, Eq (f ())) => [f S] -> [f S] -> Bool
-neqList x y = not $ eqList x y
-eqList (x:xs) (y:ys) = x =~= y && eqList xs ys
-eqList [] [] = True
-eqList _ _ = False
-
-eqMaybe:: (Annotated f, Eq (f ())) => Maybe (f S) -> Maybe (f S) -> Bool
-eqMaybe (Just x) (Just y) = x =~= y
-eqMaybe Nothing Nothing = True
-eqMaybe _ _ = False
-
-
----------------------------------------------------------------------
--- FIXITIES
-
-getFixity :: Decl a -> [Fixity]
-getFixity (InfixDecl sl a mp ops) = [Fixity (void a) (fromMaybe 9 mp) (UnQual () $ void $ f op) | op <- ops]
-    where f (VarOp _ x) = x
-          f (ConOp _ x) = x
-getFixity _ = []
-
-toInfixDecl :: Fixity -> Decl ()
-toInfixDecl (Fixity a b c) = InfixDecl () a (Just b) $ maybeToList $ VarOp () <$> fromQual c
-
-
-
--- | This extension implies the following extensions
-extensionImplies :: Extension -> [Extension]
-extensionImplies = \x -> Map.findWithDefault [] x mp
-    where mp = Map.fromList extensionImplications
-
--- | This extension is implied by the following extensions
-extensionImpliedBy :: Extension -> [Extension]
-extensionImpliedBy = \x -> Map.findWithDefault [] x mp
-    where mp = Map.fromListWith (++) [(b, [a]) | (a,bs) <- extensionImplications, b <- bs]
-
--- | (a, bs) means extension a implies all of bs.
---   Taken from https://downloads.haskell.org/~ghc/master/users-guide/glasgow_exts.html#language-options
---   In the GHC source at DynFlags.impliedXFlags
-extensionImplications :: [(Extension, [Extension])]
-extensionImplications = map (first EnableExtension) $
-    (RebindableSyntax, [DisableExtension ImplicitPrelude]) :
-    map (second (map EnableExtension))
-    [ (DerivingVia              , [DerivingStrategies])
-    , (RecordWildCards          , [DisambiguateRecordFields])
-    , (ExistentialQuantification, [ExplicitForAll])
-    , (FlexibleInstances        , [TypeSynonymInstances])
-    , (FunctionalDependencies   , [MultiParamTypeClasses])
-    , (GADTs                    , [MonoLocalBinds])
-    , (IncoherentInstances      , [OverlappingInstances])
---    Incorrect, see https://github.com/ndmitchell/hlint/issues/587
---    , (ImplicitParams           , [FlexibleContexts, FlexibleInstances])
-    , (ImpredicativeTypes       , [ExplicitForAll, RankNTypes])
-    , (LiberalTypeSynonyms      , [ExplicitForAll])
-    , (PolyKinds                , [KindSignatures])
-    , (RankNTypes               , [ExplicitForAll])
-    , (ScopedTypeVariables      , [ExplicitForAll])
-    , (TypeOperators            , [ExplicitNamespaces])
-    , (TypeFamilies             , [ExplicitNamespaces, KindSignatures, MonoLocalBinds])
-    , (TypeFamilyDependencies   , [ExplicitNamespaces, KindSignatures, MonoLocalBinds, TypeFamilies])
-    ]
diff --git a/src/Hint/All.hs b/src/Hint/All.hs
--- a/src/Hint/All.hs
+++ b/src/Hint/All.hs
@@ -1,6 +1,6 @@
 
 module Hint.All(
-    Hint(..), DeclHint, ModuHint,
+    Hint(..), ModuHint,
     resolveHints, hintRules, builtinHints
     ) where
 
@@ -19,7 +19,9 @@
 import Hint.Monad
 import Hint.Lambda
 import Hint.Bracket
+import Hint.Fixities
 import Hint.Naming
+import Hint.Negation
 import Hint.Pattern
 import Hint.Import
 import Hint.Export
@@ -31,54 +33,57 @@
 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 |
+    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
+-- and doesn't provide much value anyway.
+issue1150 = True
 
 builtin :: HintBuiltin -> Hint
 builtin x = case x of
-    -- Hse.
     HintLambda     -> decl lambdaHint
-
-    -- Ghc.
     HintImport     -> modu importHint
     HintExport     -> modu exportHint
     HintComment    -> modu commentHint
     HintPragma     -> modu pragmaHint
-    HintDuplicate  -> mods duplicateHint
+    HintDuplicate  -> if issue1150 then mempty else mods duplicateHint
     HintRestrict   -> mempty{hintModule=restrictHint}
-    HintList       -> decl' listHint
-    HintNewType    -> decl' newtypeHint
-    HintUnsafe     -> decl' unsafeHint
-    HintListRec    -> decl' listRecHint
-    HintNaming     -> decl' namingHint
-    HintBracket    -> decl' bracketHint
-    HintSmell      -> mempty{hintDecl'=smellHint,hintModule=smellModuleHint}
-    HintPattern    -> decl' patternHint
-    HintMonad      -> decl' monadHint
+    HintList       -> decl listHint
+    HintNewType    -> decl newtypeHint
+    HintUnsafe     -> decl unsafeHint
+    HintListRec    -> decl listRecHint
+    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}
-        decl' f = mempty{hintDecl'=const $ \a b c -> wrap $ f a b c}
         modu f = mempty{hintModule=const $ \a b -> wrap $ f a b}
         mods f = mempty{hintModules=const $ \a -> wrap $ f a}
 
 -- | A list of builtin hints, currently including entries such as @\"List\"@ and @\"Bracket\"@.
 builtinHints :: [(String, Hint)]
-builtinHints = [(drop 4 $ show h, builtin h) | h <- [minBound .. maxBound]]
+builtinHints = [(drop 4 $ show h, builtin h) | h <- enumerate]
 
 -- | Transform a list of 'HintBuiltin' or 'HintRule' into a 'Hint'.
 resolveHints :: [Either HintBuiltin HintRule] -> Hint
 resolveHints xs =
-  mconcat $ mempty{hintDecl'=const $ readMatch' rights} : map builtin (nubOrd lefts)
+  mconcat $ mempty{hintDecl=const $ readMatch rights} : map builtin (nubOrd lefts)
   where (lefts,rights) = partitionEithers xs
 
 -- | Transform a list of 'HintRule' into a 'Hint'.
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,14 +29,30 @@
 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 }
+issue909 = foo (\((x :: z) -> y) -> 9 + x * 7)
+issue909 = foo (\((x : z) -> y) -> 9 + x * 7) -- \(x : z -> y) -> 9 + x * 7
+issue909 = let ((x:: y) -> z) = q in q
+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
@@ -46,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
@@ -56,6 +72,7 @@
 yes = operator foo $ operator -- operator foo operator
 no = operator foo $ operator bar
 yes = return $ Record{a=b}
+no = f $ [1,2..5] -- f [1,2..5]
 
 -- $/bracket rotation tests
 yes = (b $ c d) ++ e -- b (c d) ++ e
@@ -83,133 +100,166 @@
 special = foo $ f{x=1}
 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(..),toSS')
+import Hint.Type(DeclHint,Idea(..),rawIdea,warn,suggest,Severity(..),toRefactSrcSpan,toSSA)
 import Data.Data
-import Data.Generics.Uniplate.Operations
+import Data.List.Extra
+import Data.Generics.Uniplate.DataOnly
 import Refact.Types
 
-import HsSyn
-import Outputable
-import SrcLoc
+import GHC.Hs
+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 :: 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 :: [Pat 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
-       LL l (HsPar _ x) -> x
+       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@(LL _ SectionL{}) = unsafePrettyPrint (noLoc (HsPar noExt s) :: LHsExpr GhcPs)
-prettyExpr s@(LL _ SectionR{}) = unsafePrettyPrint (noLoc (HsPar noExt 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
 
--- Dirty, should add to Brackets type class I think
-tyConToRtype :: String -> RType
-tyConToRtype "Exp"    = Expr
-tyConToRtype "Type"   = Type
-tyConToRtype "HsType" = Type
-tyConToRtype "Pat"    = Pattern
-tyConToRtype _        = Expr
-
-findType :: (Data a) => a -> RType
-findType = tyConToRtype . dataTypeName . dataTypeOf
-
 -- 'Just _' if at least one set of parens were removed. 'Nothing' if
 -- zero parens were removed.
-remParens' :: Brackets' a => a -> Maybe a
-remParens' = fmap go . remParen'
+remParens' :: Brackets (LocatedA a) => LocatedA a -> Maybe (LocatedA a)
+remParens' = fmap go . remParen
   where
-    go e = maybe e go (remParen' e)
+    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 (LL _ (HsSpliceE _ (HsTypedSplice _ HasDollar _ _) )) = True
-isPartialAtom (LL _ (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, Data (SrcSpanLess 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'
+    -- '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 =
-          (if isAtom' x then bracketError else bracketWarning) msg o x : g x
+      | root || isAtom 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)
-      | isAtom' x
-      , not $ isPartialAtom x =
+    f (Just (_, p, _)) o@(remParens' -> Just x)
+      | isAtom 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 o) (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 (unLoc v)
-        r = Replace typ (toSS' v) [("x", toSS' x)] "x"
+        typ = findType v
+        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)) <- zip [0..] $ holes o]
+    g o = concat [f (Just (i, o, gen)) x | (i, (x, gen)) <- zipFrom 0 $ holes o]
 
-bracketWarning :: (HasSrcSpan a, HasSrcSpan b, Data (SrcSpanLess b), Outputable a, Outputable b) => String -> a -> b -> Idea
 bracketWarning msg o x =
-  suggest' msg o x [Replace (findType (unLoc 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, Data (SrcSpanLess b), Outputable a, Outputable 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 (unLoc 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@(LL loc f@ConDeclField{cd_fld_type=v@(LL l (HsParTy _ c))}) =
-   let r = LL loc (f{cd_fld_type=c}) :: LConDeclField GhcPs in
-   [rawIdea' Suggestion "Redundant bracket" loc
+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" (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).
-     ppr_fld (LL _ ConDeclField { cd_fld_names = ns, cd_fld_type = ty, cd_fld_doc = doc })
-       = ppr_names ns <+> dcolon <+> ppr ty <+> ppr_mbDoc doc
-     ppr_fld (LL _ (XConDeclField x)) = ppr x
-     ppr_fld _ = undefined -- '{-# COMPLETE LL #-}'
+     -- 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 })
+       = pprMaybeWithDoc doc (ppr_names ns <+> dcolon <+> ppr ty)
+     ppr_fld (L _ (XConDeclField x)) = ppr x
 
      ppr_names [n] = ppr n
      ppr_names ns = sep (punctuate comma (map ppr ns))
@@ -220,26 +270,33 @@
 dollar :: LHsExpr GhcPs -> [Idea]
 dollar = concatMap f . universe
   where
-    f x = [ suggest' "Redundant $" x y [r]| o@(LL loc (OpApp _ a d b)) <- [x], isDol d
-            , let y = noLoc (HsApp noExt 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"]
+    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 (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]
-            |(t, e@(LL _ (HsPar _ (LL _ (OpApp _ a1 op1 a2))))) <- splitInfix x
+          [ 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
+            , isVar a1 || isApp a1 || isPar a1, not $ isAtom a2
             , varToStr a1 /= "select" -- special case for esqueleto, see #224
-            , let y = noLoc $ HsApp noExt a1 (noLoc (HsPar noExt 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 []
-          | LL _ (OpApp _ (LL _ (HsPar _ o1@(LL _ (OpApp _ v1 (isDot -> True) v2)))) o2 v3) <- [x], varToStr o2 == "<$>"
-          , let y = noLoc (OpApp noExt o1 o2 v3) :: LHsExpr GhcPs]
+          [ (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 = noLocA (OpApp noExtField o1 o2 v3) :: LHsExpr GhcPs
+          , let r = Replace Expr (toRefactSrcSpan (locA locPar)) [("a", toRefactSrcSpan (locA locNoPar))] "a"]
+          ++
+          [ 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 = 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 (LL l (OpApp _ lhs op rhs)) =
-  [(LL l . OpApp noExt lhs op, rhs), (\lhs -> LL l (OpApp noExt lhs op rhs), lhs)]
+splitInfix (L l (OpApp _ lhs op rhs)) =
+  [(L l . OpApp noExtField lhs op, rhs), (\lhs -> L l (OpApp noExtField lhs op rhs), lhs)]
 splitInfix _ = []
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,19 +33,21 @@
 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 ++ " #"]
                where
                  isMultiline = isCommentMultiline comm
                  s = commentText comm
-                 name = takeWhile (\x -> isAlphaNum x || x == '_') $ dropWhile isSpace s
+                 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 (ghcSpanToHSE 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 #-}
 
@@ -5,70 +6,73 @@
 Find bindings within a let, and lists of statements
 If you have n the same, error out
 
-<TEST>
+<TEST_DISABLED_1150>
+foo = a where {a = 1; b = 2; c = 3} \
+bar = a where {a = 1; b = 2; c = 3} -- ???
 main = do a; a; a; a
 main = do a; a; a; a; a; a -- ???
 main = do a; a; a; a; a; a; a -- ???
 main = do (do b; a; a; a); do (do c; a; a; a) -- ???
 main = do a; a; a; b; a; a; a -- ???
 main = do a; a; a; b; a; a
-foo = a where {a = 1; b = 2; c = 3}; bar = a where {a = 1; b = 2; c = 3} -- ???
 {-# ANN main "HLint: ignore Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ???
 {-# HLINT ignore main "Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ???
 {- HLINT ignore main "Reduce duplication" -}; main = do a; a; a; a; a; a -- @Ignore ???
-</TEST>
+</TEST_DISABLED_1150>
 -}
 
 
 module Hint.Duplicate(duplicateHint) where
 
-import Hint.Type (CrossHint, ModuleEx(..), Idea(..),rawIdeaN',Severity(Suggestion,Warning),showSrcLoc,ghcSrcLocToHSE)
+import Hint.Type (CrossHint, ModuleEx(..), Idea(..),rawIdeaN,Severity(Suggestion,Warning))
 import Data.Data
-import Data.Generics.Uniplate.Operations
+import Data.Generics.Uniplate.DataOnly
 import Data.Default
 import Data.Maybe
 import Data.Tuple.Extra
 import Data.List hiding (find)
-import qualified Data.Map as Map
+import Data.List.NonEmpty qualified as NE
+import Data.Map qualified as Map
 
-import SrcLoc
-import HsSyn
-import Outputable
-import Bag
+import GHC.Types.SrcLoc
+import GHC.Hs
+import GHC.Utils.Outputable
 import GHC.Util
+import Language.Haskell.GhclibParserEx.GHC.Hs
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 duplicateHint :: CrossHint
 duplicateHint ms =
    -- Do expressions.
    dupes [ (m, d, y)
          | (m, d, x) <- ds
-         , HsDo _ _ (LL _ y) :: HsExpr GhcPs <- universeBi x
+         , HsDo _ _ (L _ y) :: HsExpr GhcPs <- universeBi x
          ] ++
   -- Bindings in a 'let' expression or a 'where' clause.
    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'
+    [(rawIdeaN
         (if length xs >= 5 then Hint.Type.Warning else Suggestion)
         "Reduce duplication" p1
         (unlines $ map unsafePrettyPrint xs)
-        (Just $ "Combine with " ++
-         showSrcLoc (ghcSrcLocToHSE (srcSpanStart p2))) []
+        (Just $ "Combine with " ++ showSrcSpan p2)
+        []
      ){ideaModule = [m1, m2], ideaDecl = [d1, d2]}
     | ((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
@@ -93,19 +97,23 @@
 duplicateOrdered threshold xs = concat $ concat $ snd $ mapAccumL f (Dupe def Map.empty) xs
     where
         f :: Dupe pos val -> [(pos, val)] -> (Dupe pos val, [[(pos, pos, [val])]])
-        f d xs = second overlaps $ mapAccumL (g pos) d $ takeWhile ((>= threshold) . length) $ tails xs
+        f d xs = second overlaps $ mapAccumL (g pos) d $ onlyAtLeast threshold $ tails xs
             where pos = Map.fromList $ zip (map fst xs) [0..]
 
-        g :: Map.Map pos Int -> Dupe pos val -> [(pos, val)] -> (Dupe pos val, [(pos, pos, [val])])
+        g :: Map.Map pos Int -> Dupe pos val -> NE.NonEmpty (pos, val) -> (Dupe pos val, [(pos, pos, [val])])
         g pos d xs = (d2, res)
             where
                 res = [(p,pme,take mx vs) | i >= threshold
                       ,let mx = maybe i (\x -> min i $ (pos Map.! pme) - x) $ Map.lookup p pos
                       ,mx >= threshold]
-                vs = map snd xs
+                vs = NE.toList $ snd <$> xs
                 (p,i) = find vs d
-                pme = fst $ head xs
+                pme = fst $ NE.head xs
                 d2 = add pme vs d
+
+        onlyAtLeast n = mapMaybe $ \l -> case l of
+           x:xs | length l >= n -> Just (x NE.:| xs)
+           _ -> Nothing
 
         overlaps (x@((_,_,n):_):xs) = x : overlaps (drop (length n - 1) xs)
         overlaps (x:xs) = x : overlaps xs
diff --git a/src/Hint/Export.hs b/src/Hint/Export.hs
--- a/src/Hint/Export.hs
+++ b/src/Hint/Export.hs
@@ -13,35 +13,34 @@
 
 module Hint.Export(exportHint) where
 
-import Hint.Type(ModuHint, ModuleEx(..),ideaNote,ignore',Note(..))
+import Hint.Type(ModuHint, ModuleEx(..),ideaNote,ignore,Note(..))
 
-import HsSyn
-import Module
-import SrcLoc
-import OccName
-import RdrName
+import GHC.Hs
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Occurrence
+import GHC.Types.Name.Reader
 
 exportHint :: ModuHint
-exportHint _ (ModuleEx _ _ (LL 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 noExt name)] )} in
-      [(ignore' "Use module export list" (L s o) (noLoc r) []){ideaNote = [Note "an explicit list is usually better"]}]
+      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]
   , modName <- moduleNameString (unLoc name)
-  , names <- [ moduleNameString (unLoc n) | (LL _ (IEModuleContents _ n)) <- mods]
+  , names <- [ moduleNameString (unLoc n) | (L _ (IEModuleContents _ n)) <- mods]
   , exports' <- [x | x <- xs, not (matchesModName modName x)]
   , modName `elem` names =
       let dots = mkRdrUnqual (mkVarOcc " ... ")
-          r = o{ hsmodExports = Just (noLoc (noLoc (IEVar noExt (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) []]
+        [ignore "Use explicit module export list" (L s o) (noLoc r) []]
       where
-          o = m{hsmodImports=[], hsmodDecls=[], hsmodDeprecMessage=Nothing, hsmodHaddockModHeader=Nothing }
-          isMod (LL _ (IEModuleContents _ _)) = True
+          o = m{hsmodImports=[], hsmodDecls=[] }
+          isMod (L _ (IEModuleContents _ _)) = True
           isMod _ = False
 
-          matchesModName m (LL _ (IEModuleContents _ (L _ n))) = moduleNameString n == m
+          matchesModName m (L _ (IEModuleContents _ (L _ n))) = moduleNameString n == m
           matchesModName _ _ = False
 
 exportHint _ _ = []
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 ScopedTypeVariables #-}
+{-# 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 #-} \
@@ -33,6 +36,33 @@
 foo x = let !y = x in y
 {-# LANGUAGE BangPatterns #-} \
 data Foo = Foo !Int --
+{-# LANGUAGE TypeOperators #-} \
+data (<+>) a b = Foo a b
+{-# LANGUAGE TypeOperators #-} \
+data Foo a b = a :+ b --
+{-# LANGUAGE TypeOperators #-} \
+type (<+>) a b = Foo a b
+{-# LANGUAGE TypeOperators #-} \
+type Foo a b = a :+ b
+{-# LANGUAGE TypeOperators, TypeFamilies #-} \
+type family Foo a b :: Type where Foo a b = a :+ b
+{-# LANGUAGE TypeOperators, TypeFamilies #-} \
+type family Foo a b :: Type where Foo a b = (<+>) a b -- {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators, TypeFamilies #-} \
+class Foo a where data (<+>) a
+{-# LANGUAGE TypeOperators, TypeFamilies #-} \
+class Foo a where foo :: a -> Int <+> Bool
+{-# LANGUAGE TypeOperators #-} \
+class (<+>) a where
+{-# LANGUAGE TypeOperators #-} \
+foo :: Int -> Double <+> Bool \
+foo x = y
+{-# LANGUAGE TypeOperators #-} \
+foo :: Int -> (<+>) Double Bool \
+foo x = y --
+{-# LANGUAGE TypeOperators #-} \
+(<+>) :: Int -> Int -> Int \
+x <+> y = x + y --
 {-# LANGUAGE RecordWildCards #-} \
 record field = Record{..}
 {-# LANGUAGE RecordWildCards #-} \
@@ -68,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 #-} \
@@ -78,6 +110,13 @@
 f x = case x of (# a, b #) -> a
 {-# LANGUAGE GeneralizedNewtypeDeriving,UnboxedTuples #-} \
 newtype T m a = T (m a) deriving (PrimMonad)
+{-# LANGUAGE InstanceSigs #-} \
+instance Eq a => Eq (T a) where \
+  (==) :: T a -> T a -> Bool \
+  (==) (T x) (T y) = x==y
+{-# LANGUAGE InstanceSigs #-} \
+instance Eq a => Eq (T a) where \
+  (==) (T x) (T y) = x==y --
 {-# LANGUAGE DefaultSignatures #-} \
 class Val a where; val :: a --
 {-# LANGUAGE DefaultSignatures #-} \
@@ -86,6 +125,8 @@
 foo = id --
 {-# LANGUAGE TypeApplications #-} \
 foo = id @Int
+{-# LANGUAGE TypeApplications #-} \
+x :: Typeable b => TypeRep @Bool b
 {-# LANGUAGE LambdaCase #-} \
 foo = id --
 {-# LANGUAGE LambdaCase #-} \
@@ -104,6 +145,18 @@
 main = "test"
 {-# LANGUAGE OverloadedStrings #-} \
 main = id --
+{-# LANGUAGE OverloadedLists #-} \
+main = []
+{-# LANGUAGE OverloadedLists #-} \
+main = [1]
+{-# LANGUAGE OverloadedLists #-} \
+main [1] = True
+{-# LANGUAGE OverloadedLists #-} \
+main = id --
+{-# LANGUAGE OverloadedLabels #-} \
+main = #foo
+{-# LANGUAGE OverloadedLabels #-} \
+main = id --
 {-# LANGUAGE DeriveAnyClass #-} \
 main = id --
 {-# LANGUAGE DeriveAnyClass #-} \
@@ -135,93 +188,196 @@
 data Set (cxt :: * -> *) a = Set [a] -- @Note Extension KindSignatures is implied by PolyKinds
 {-# LANGUAGE QuasiQuotes, OverloadedStrings #-} \
 main = putStrLn [f|{T.intercalate "blah" []}|]
+{-# LANGUAGE NamedFieldPuns #-} \
+foo = x{bar}
+{-# LANGUAGE PatternSynonyms #-} \
+module Foo (pattern Bar) where x = 42
+{-# LANGUAGE PatternSynonyms #-} \
+import Foo (pattern Bar); x = 42
+{-# LANGUAGE PatternSynonyms #-} \
+pattern Foo s <- Bar s _ where Foo s = Bar s s
+{-# LANGUAGE PatternSynonyms #-} \
+x = 42 --
+{-# LANGUAGE MultiWayIf #-} \
+x = if | b1 -> v1 | b2 -> v2 | otherwise -> v3
+{-# LANGUAGE MultiWayIf #-} \
+x = if b1 then v1 else if b2 then v2 else v3 --
+static = 42
+{-# LANGUAGE NamedFieldPuns #-} \
+foo Foo{x} = x
+{-# LANGUAGE NamedFieldPuns #-} \
+foo = Foo{x}
+{-# LANGUAGE NamedFieldPuns #-} \
+foo = bar{x}
+{-# LANGUAGE NamedFieldPuns #-} --
+{-# LANGUAGE NumericUnderscores #-} \
+lessThanPi = (< 3.141_592_653_589_793)
+{-# LANGUAGE NumericUnderscores #-} \
+oneMillion = 0xf4__240
+{-# LANGUAGE NumericUnderscores #-} \
+avogadro = 6.022140857e+23 --
+{-# LANGUAGE StaticPointers #-} \
+static = 42 --
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE Trustworthy, NamedFieldPuns #-} -- {-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE Haskell2010 #-}
+{-# LANGUAGE NoStarIsType, ExplicitNamespaces #-} \
+import GHC.TypeLits(KnownNat, type (+), type (*))
+{-# LANGUAGE LambdaCase, MultiWayIf, NoRebindableSyntax #-} \
+foo = \case True -> 3 -- {-# LANGUAGE LambdaCase, NoRebindableSyntax #-}
+{-# LANGUAGE ImportQualifiedPost #-} \
+import Control.Monad qualified as CM
+{-# LANGUAGE ImportQualifiedPost #-} \
+import qualified Control.Monad as CM hiding (mapM) \
+import Data.Foldable -- @NoRefactor: refactor only works when using GHC 8.10
+{-# LANGUAGE StandaloneKindSignatures #-} \
+type T :: (k -> Type) -> k -> Type \
+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,extensionImpliedBy,extensionImplies)
-import Language.Haskell.Exts.Extension
+import Hint.Type(ModuHint,rawIdea,Severity(Warning),Note(..),toSSAnc,ghcModule,modComments,firstDeclComments)
+import Extension
 
-import Data.Generics.Uniplate.Operations
+import Data.Generics.Uniplate.DataOnly
 import Control.Monad.Extra
+import Data.Maybe
 import Data.List.Extra
-import Data.Ratio
 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 HsSyn
-import BasicTypes
-import Class
-import RdrName
-import OccName
-import ForeignCall
+import GHC.Data.FastString
+import GHC.Types.SrcLoc
+import GHC.Types.SourceText
+import GHC.Hs
+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.Type
+import Language.Haskell.GhclibParserEx.GHC.Hs.Decls
+import Language.Haskell.GhclibParserEx.GHC.Hs.Binds
+import Language.Haskell.GhclibParserEx.GHC.Hs.ImpExp
+import Language.Haskell.GhclibParserEx.GHC.Driver.Session
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
 extensionsHint :: ModuHint
 extensionsHint _ x =
-    [ rawIdea' Hint.Type.Warning "Unused LANGUAGE pragma"
-        sl
-        (comment (mkLangExts sl exts))
+    [
+        rawIdea Hint.Type.Warning "Unused LANGUAGE pragma"
+        (RealSrcSpan (epaLocationRealSrcSpan sl) GHC.Data.Strict.Nothing)
+        (comment_ (mkLanguagePragmas sl exts))
         (Just newPragma)
-        ( [RequiresExtension $ prettyExtension gone | x <- before \\ after, gone <- Map.findWithDefault [] x disappear] ++
-            [ Note $ "Extension " ++ prettyExtension x ++ " is " ++ reason x
-            | x <- explainedRemovals])
-        [ModifyComment (toSS' (mkLangExts sl exts)) newPragma]
-    | (LL sl _,  exts) <- langExts $ pragmas (ghcAnnotations x)
-    , let before = map parseExtension exts
-    , let after = filter (`Set.member` keep) before
+        ( [RequiresExtension (show gone) | (_, Just x) <- before \\ after, gone <- Map.findWithDefault [] x disappear] ++
+            [ Note $ "Extension " ++ s ++ " is " ++ reason x
+            | (s, Just x) <- explainedRemovals])
+        [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
     , let explainedRemovals
-            | null after && not (any (`Map.member` implied) before) = []
+            | null after && not (any (`Map.member` implied) $ mapMaybe snd before) = []
             | otherwise = before \\ after
     , let newPragma =
-            if null after then "" else comment (mkLangExts sl $ map prettyExtension 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 [ parseExtension e
-                              | let exts = concatMap snd $ langExts (pragmas (ghcAnnotations x))
-                              , e <- exts ]
+    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
     implied = Map.fromList
         [ (e, a)
         | e <- Set.toList useful
-        , a:_ <- [filter (`Set.member` useful) $ extensionImpliedBy e]]
+        , a:_ <- [filter (`Set.member` useful) $ extensionImpliedEnabledBy e]
+        ]
     -- Those we should keep.
     keep :: Set.Set Extension
     keep =  useful `Set.difference` Map.keysSet implied
     -- The meaning of (a,b) is a used to imply b, but has gone, so
     -- suggest enabling b.
+    disappear :: Map.Map Extension [Extension]
     disappear =
         Map.fromListWith (++) $
         nubOrdOn snd -- Only keep one instance for each of a.
         [ (e, [a])
         | e <- Set.toList $ extensions `Set.difference` keep
-        , a <- extensionImplies e
+        , a <- fst $ extensionImplies e
         , a `Set.notMember` useful
         , usedTH || usedExt a (ghcModule x)
         ]
     reason :: Extension -> String
     reason x =
       case Map.lookup x implied of
-        Just a -> "implied by " ++ prettyExtension a
+        Just a -> "implied by " ++ show a
         Nothing -> "not used"
 
 deriveHaskell = ["Eq","Ord","Enum","Ix","Bounded","Read","Show"]
@@ -238,99 +394,139 @@
 deriveStock = deriveHaskell ++ deriveGenerics ++ deriveCategory
 
 usedExt :: Extension -> Located (HsModule GhcPs) -> Bool
-usedExt (EnableExtension x) = used x
-usedExt (UnknownExtension "NumDecimals") = hasS isWholeFrac
-usedExt (UnknownExtension "DeriveLift") = hasDerive ["Lift"]
-usedExt (UnknownExtension "DeriveAnyClass") = not . null . derivesAnyclass . derives
-usedExt _ = const True
+usedExt NumDecimals = hasS isWholeFrac
+  -- Only whole number fractions are permitted by NumDecimals
+  -- extension.  Anything not-whole raises an error.
+usedExt DeriveLift = hasDerive ["Lift"]
+usedExt DeriveAnyClass = not . null . derivesAnyclass . derives
+usedExt x = used x
 
-used :: KnownExtension -> Located (HsModule GhcPs) -> Bool
+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 _ (LL _ []) _)) = True
-    f (HsLamCase _ (MG _ (LL _ []) _)) = 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 BangPatterns = hasS isPBangPat ||^ hasS isStrictMatch
+used TemplateHaskell = hasS (not . isQuasiQuoteSplice) ||^ hasS isTypedSplice
+used TemplateHaskellQuotes = hasS f
   where
-    isStrictMatch :: HsMatchContext RdrName -> Bool
-    isStrictMatch FunRhs{mc_strictness=SrcStrict} = True
-    isStrictMatch _ = False
-used TemplateHaskell = hasT2' (un :: (HsBracket GhcPs, HsSplice GhcPs)) ||^ hasS f ||^ hasS isSpliceDecl
-    where
-      f :: HsBracket GhcPs -> Bool
-      f VarBr{} = True
-      f TypBr{} = True
-      f _ = False
+    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 -- new ctor
     g :: [GuardLStmt GhcPs] -> Bool
     g [] = False
-    g [LL _ BodyStmt{}] = False
+    g [L _ BodyStmt{}] = False
     g _ = True
-used StandaloneDeriving = hasS isDerivD'
-used PatternSignatures = hasS isPatTypeSig'
-used RecordWildCards = hasS hasFieldsDotDot ||^ hasS hasPFieldsDotDot'
-used RecordPuns = hasS isPFieldPun' ||^ hasS isFieldPun
-used NamedFieldPuns = hasS isPFieldPun' ||^ hasS isFieldPun
-used UnboxedTuples = has isUnboxedTuple' ||^ has (== Unboxed) ||^ hasS isDeriving
-    where
-        -- detect if there are deriving declarations or data ... deriving stuff
-        -- by looking for the deriving strategy both contain (even if its Nothing)
-        -- see https://github.com/ndmitchell/hlint/issues/833 for why we care
-        isDeriving :: Maybe (LDerivStrategy GhcPs) -> Bool
-        isDeriving _ = True
+used StandaloneDeriving = hasS isDerivD
+used TypeOperators = hasS tyOpInSig ||^ hasS tyOpInDecl
+  where
+    tyOpInSig :: HsType GhcPs -> Bool
+    tyOpInSig = \case
+      HsOpTy{} -> True; _ -> False
+
+    tyOpInDecl :: HsDecl GhcPs -> Bool
+    tyOpInDecl = \case
+      (TyClD _ (FamDecl _ FamilyDecl{fdLName})) -> isOp fdLName
+      (TyClD _ SynDecl{tcdLName}) -> isOp tcdLName
+      (TyClD _ DataDecl{tcdLName}) -> isOp tcdLName
+      (TyClD _ ClassDecl{tcdLName, tcdATs}) -> any isOp (tcdLName : [fdLName famDecl | L _ famDecl <- tcdATs])
+      _ -> False
+
+    isOp (L _ name) = isSymbolRdrName name
+
+used RecordWildCards = hasS hasFieldsDotDot ||^ hasS hasPFieldsDotDot
+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
+      -- by looking for the deriving strategy both contain (even if its Nothing)
+      -- see https://github.com/ndmitchell/hlint/issues/833 for why we care
+      isDeriving :: Maybe (LDerivStrategy GhcPs) -> Bool
+      isDeriving _ = True
 used PackageImports = hasS f
-    where
-        f :: ImportDecl GhcPs -> Bool
-        f ImportDecl{ideclPkgQual=Just _} = True
-        f _ = False
-used QuasiQuotes = hasS isQuasiQuote ||^ hasS isTyQuasiQuote'
-used ViewPatterns = hasS isPViewPat'
-used DefaultSignatures = hasS isClsDefSig'
+  where
+      f :: ImportDecl GhcPs -> Bool
+      f ImportDecl{ideclPkgQual=RawPkgQual _} = True
+      f _ = False
+used QuasiQuotes = hasS isQuasiQuoteExpr ||^ hasS isTyQuasiQuote
+used ViewPatterns = hasS isPViewPat
+used InstanceSigs = hasS f
+  where
+    f :: HsDecl GhcPs -> Bool
+    f (InstD _ decl) = hasT (un :: Sig GhcPs) decl
+    f _ = False
+used DefaultSignatures = hasS isClsDefSig
 used DeriveDataTypeable = hasDerive ["Data","Typeable"]
 used DeriveFunctor = hasDerive ["Functor"]
 used DeriveFoldable = hasDerive ["Foldable"]
 used DeriveTraversable = hasDerive ["Traversable","Foldable","Functor"]
 used DeriveGeneric = hasDerive ["Generic","Generic1"]
 used GeneralizedNewtypeDeriving = not . null . derivesNewtype' . derives
+used MultiWayIf = hasS isMultiIf
+used NumericUnderscores = hasS f
+  where
+    f :: OverLitVal -> Bool
+    f (HsIntegral (IL (SourceText t) _ _)) = '_' `elem` unpackFS t
+    f (HsFractional (FL (SourceText t) _ _ _ _)) = '_' `elem` unpackFS t
+    f _ = False
+
 used LambdaCase = hasS isLCase
 used TupleSections = hasS isTupleSection
 used OverloadedStrings = hasS isString
-used Arrows = hasS f
+used OverloadedLists = hasS isListExpr ||^ hasS isListPat
   where
-    f :: HsExpr GhcPs -> Bool
-    f HsProc{} = True
-    f HsArrApp{} = True
-    f _ = False
-used TransformListComp = hasS f
-    where
-      f :: StmtLR GhcPs GhcPs (LHsExpr GhcPs) -> Bool
-      f TransStmt{} = True
-      f _ = False
+    isListExpr :: HsExpr GhcPs -> Bool
+    isListExpr (HsVar _ n) = rdrNameStr n == "[]"
+    isListExpr ExplicitList{} = True
+    isListExpr ArithSeq{} = True
+    isListExpr _ = False
+
+    isListPat :: Pat GhcPs -> Bool
+    isListPat ListPat{} = True
+    isListPat _ = False
+
+used OverloadedLabels = hasS isOverLabel
+
+used Arrows = hasS isProc
+used TransformListComp = hasS isTransStmt
 used MagicHash = hasS f ||^ hasS isPrimLiteral
-    where
-      f :: RdrName -> Bool
-      f s = "#" `isSuffixOf` (occNameString . rdrNameOcc) s
--- For forwards compatibility, if things ever get added to the
--- extension enumeration.
-used x = usedExt $ UnknownExtension $ show x
+  where
+    f :: RdrName -> Bool
+    f s = "#" `isSuffixOf` occNameStr s
+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
+
 hasDerive :: [String] -> Located (HsModule GhcPs) -> Bool
 hasDerive want = any (`elem` want) . derivesStock' . derives
 
@@ -350,58 +546,47 @@
 
 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
-    ,derivesNewtype' = if maybe True isNewType' nt then filter (`notElem` noDeriveNewtype) xs else []}
+    ,derivesNewtype' = if maybe True isNewType nt then filter (`notElem` noDeriveNewtype) xs else []}
     where (stock, other) = partition (`elem` deriveStock) xs
 
 derives :: Located (HsModule GhcPs) -> Derives
-derives (LL _ m) =  mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m)
+derives (L _ m) =  mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m)
   where
-    idecl :: Located (DataFamInstDecl GhcPs) -> Derives
-    idecl (LL _ (DataFamInstDecl (HsIB _ FamEqn {feqn_rhs=HsDataDefn {dd_ND=dn, dd_derivs=(LL _ 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 (LL _ (TyClD _ (DataDecl _ _ _ _ HsDataDefn {dd_ND=dn, dd_derivs=(LL _ ds)}))) = g dn ds -- Data declaration.
-    decl (LL _ (DerivD _ (DerivDecl _ (HsWC _ sig) strategy _))) = addDerives Nothing (fmap unLoc strategy) [derivedToStr sig] -- A deriving 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 | LL _ (HsDerivingClause _ strategy (LL _ 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 (LL _ (HsQualTy _ _ a)) = ih a
-        ih (LL _ (HsParTy _ a)) = ih a
-        ih (LL _ (HsAppTy _ a _)) = ih a
-        ih (LL _ (HsTyVar _ _ a)) = unsafePrettyPrint $ unqual' a
-        ih (LL _ a) = unsafePrettyPrint a -- I don't anticipate this case is called.
-        ih _ = "" -- {-# COMPLETE LL #-}
-    derivedToStr _ = "" -- new ctor
-
-derives _ = mempty -- {-# COMPLETE LL #-}
+        ih (L _ (HsQualTy _ _ a)) = ih a
+        ih (L _ (HsParTy _ a)) = ih a
+        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.
 
 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
-
-has f = any f . universeBi
-
--- Only whole number fractions are permitted by NumDecimals extension.
--- Anything not-whole raises an error.
-isWholeFrac :: HsExpr GhcPs -> Bool
-isWholeFrac (HsLit _ (HsRat _ (FL _ _ v) _)) = denominator v == 1
-isWholeFrac (HsOverLit _ (OverLit _ (HsFractional (FL _ _ v)) _)) = denominator v == 1
-isWholeFrac _ = False
diff --git a/src/Hint/Fixities.hs b/src/Hint/Fixities.hs
new file mode 100644
--- /dev/null
+++ b/src/Hint/Fixities.hs
@@ -0,0 +1,78 @@
+{-
+
+Raise a warning if you have redundant brackets in nested infix expressions.
+
+<TEST>
+yes = 1 + (2 * 3) -- @Ignore 1 + 2 * 3
+yes = (2 * 3) + 1 -- @Ignore 2 * 3 + 1
+no = (1 + 2) * 3
+no = 3 * (1 + 2)
+no = 1 + 2 * 3
+no = 2 * 3 + 1
+yes = (a >>= f) >>= g -- @Ignore a >>= f >>= g
+no = (a >>= \x -> b) >>= g
+</TEST>
+-}
+
+module Hint.Fixities(fixitiesHint) where
+
+import Hint.Type(DeclHint,Idea(..),rawIdea,toSSA)
+import Config.Type
+import Control.Monad
+import Data.List.Extra
+import Data.Map
+import Data.Generics.Uniplate.DataOnly
+import Refact.Types
+
+import GHC.Types.Fixity(compareFixity)
+import Fixity
+import GHC.Hs
+import GHC.Util
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+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 Data.Map.singleton (toFixity x)
+     getFixity _ = mempty
+
+infixBracket :: Map String Fixity -> LHsExpr GhcPs -> [Idea]
+infixBracket fixities = f Nothing
+  where
+    msg = "Redundant bracket due to operator fixities"
+    f p o = cur p o <> concat [f (Just (i, o, gen)) x | (i, (x, gen)) <- zipFrom 0 $ holes o]
+    cur p v = do
+      Just (i, o, gen) <- [p]
+      Just x <- [remParen v]
+      guard $ redundantInfixBracket fixities i o x
+      pure $
+        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
+    | L _ (OpApp _ _ (L _ (HsVar _ (L _ (Unqual p)))) _) <- parent
+    , L _ (OpApp _ _ (L _ (HsVar _ (L _ (Unqual c)))) (L _ cr)) <- child =
+    let (lop, rop)
+            | i == 0 = (c, p)
+            | otherwise = (p, c)
+    in
+    case compareFixity <$> (fixities Data.Map.!? occNameString lop) <*> (fixities Data.Map.!? occNameString rop) of
+    Just (False, r)
+        | i == 0 -> not (needParenAsChild cr || r)
+        | otherwise -> r
+    _ -> False
+    | otherwise = False
+
+needParenAsChild :: HsExpr p -> Bool
+needParenAsChild HsLet{} = True
+needParenAsChild HsDo{} = True
+needParenAsChild HsLam{} = 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,4 +1,5 @@
-{-# LANGUAGE PatternGuards, ScopedTypeVariables, RecordWildCards #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase, PatternGuards, RecordWildCards #-}
 {-
     Reduce the number of import declarations.
     Two import declarations can be combined if:
@@ -28,54 +29,55 @@
 import qualified A; import A
 import B; import A; import A -- import A
 import A hiding(Foo); import A hiding(Bar)
-import List -- import Data.List
-import qualified List -- import qualified Data.List as List
-import Char(foo) -- import Data.Char(foo)
-import IO(foo)
-import IO as X -- import System.IO as X; import System.IO.Error as X; import Control.Exception  as X (bracket,bracket_)
+import A (foo) \
+import A (bar) \
+import A (baz) -- import A ( foo, bar, baz )
 </TEST>
 -}
 
 
 module Hint.Import(importHint) where
 
-import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),suggest',toSS',rawIdea',rawIdeaN')
+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.Operations
+import Data.Generics.Uniplate.DataOnly
 import Data.Maybe
 import Control.Applicative
 import Prelude
 
-import FastString
-import BasicTypes
-import RdrName
-import Module
-import HsSyn
-import SrcLoc
-import GHC.Util
+import GHC.Data.FastString
+import GHC.Types.SourceText
+import GHC.Hs
+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 ++
-  -- Ideas for replacing deprecated imports by their preferred
-  -- equivalents.
-  concatMap preferHierarchicalImports ms
+  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
 
@@ -84,7 +86,9 @@
 simplify [] = Nothing
 simplify (x : xs) = case simplifyHead x xs of
     Nothing -> first (x:) <$> simplify xs
-    Just (xs, rs) -> Just $ maybe (xs, rs) (second (++ rs)) $ simplify xs
+    Just (xs, rs) ->
+      let deletions = filter (\case Delete{} -> True; _ -> False) rs
+       in Just $ maybe (xs, rs) (second (++ deletions)) $ simplify xs
 
 simplifyHead :: LImportDecl GhcPs
              -> [LImportDecl GhcPs]
@@ -97,31 +101,31 @@
 combine :: LImportDecl GhcPs
         -> LImportDecl GhcPs
         -> Maybe (LImportDecl GhcPs, [Refactoring R.SrcSpan])
-combine x@(LL _ x') y@(LL _ y')
+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 = noLoc 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'.
-  | not (ideclQualified x'), qual, specs, length ass == 1 =
+  | 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
@@ -129,61 +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')
-combine _ _ = Nothing -- {-# COMPLETE LL #-}
+        specs = transformBi (const noSrcSpan) (ideclImportList x') ==
+                    transformBi (const noSrcSpan) (ideclImportList y')
 
 stripRedundantAlias :: LImportDecl GhcPs -> [Idea]
-stripRedundantAlias x@(LL 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 _ = []
-
-preferHierarchicalImports :: LImportDecl GhcPs -> [Idea]
-preferHierarchicalImports x@(LL loc i@ImportDecl{ideclName=L _ n,ideclPkgQual=Nothing})
-  -- Suggest 'import IO' be rewritten 'import System.IO, import
-  -- System.IO.Error, import Control.Exception(bracket, bracket_)'.
-  | n == mkModuleName "IO" && isNothing (ideclHiding i) =
-      [rawIdeaN' Suggestion "Use hierarchical imports" loc
-      (trimStart $ unsafePrettyPrint i) (
-          Just $ unlines $ map (trimStart . unsafePrettyPrint)
-          [ f "System.IO" Nothing, f "System.IO.Error" Nothing
-          , f "Control.Exception" $ Just (False, noLoc [mkLIE x | x <- ["bracket","bracket_"]])]) []]
-  -- Suggest that a module import like 'Monad' should be rewritten with
-  -- its hiearchical equivalent e.g. 'Control.Monad'.
-  | Just y <- lookup (moduleNameString n) newNames =
-    let newModuleName = y ++ "." ++ moduleNameString n
-        r = [Replace R.ModuleName (toSS' x) [] newModuleName] in
-    [suggest' "Use hierarchical imports"
-     x (noLoc (desugarQual i){ideclName=noLoc (mkModuleName newModuleName)} :: LImportDecl GhcPs) r]
-  where
-    -- Substitute a new module name.
-    f a b = (desugarQual i){ideclName=noLoc (mkModuleName a), ideclHiding=b}
-    -- Wrap a literal name into an 'IE' (import/export) value.
-    mkLIE :: String -> LIE GhcPs
-    mkLIE n = noLoc $ IEVar noExt (noLoc (IEName (noLoc (mkVarUnqual (fsLit n)))))
-    -- Rewrite 'import qualified X' as 'import qualified X as X'.
-    desugarQual :: ImportDecl GhcPs -> ImportDecl GhcPs
-    desugarQual i
-      | ideclQualified i && isNothing (ideclAs i) = i{ideclAs = Just (ideclName i)}
-      | otherwise = i
-
-preferHierarchicalImports _ = []
-
-newNames :: [(String, String)]
-newNames = let (*) = flip (,) in
-    ["Control" * "Monad"
-    ,"Data" * "Char"
-    ,"Data" * "List"
-    ,"Data" * "Maybe"
-    ,"Data" * "Ratio"
-    ,"System" * "Directory"
-
-    -- Special, see bug https://code.google.com/archive/p/ndmitchell/issues/393
-    -- ,"System" * "IO"
-
-    -- Do not encourage use of old-locale/old-time over haskell98
-    -- ,"System" * "Locale"
-    -- ,"System" * "Time"
-    ]
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 ViewPatterns, PatternGuards #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase, PatternGuards, TupleSections, ViewPatterns #-}
 
 {-
     Concept:
@@ -8,7 +9,7 @@
     Rules:
     fun a = \x -> y  -- promote lambdas, provided no where's outside the lambda
     fun x = y x  -- eta reduce, x /= mr and foo /= symbol
-    \x -> y x  -- eta reduce
+    \x -> y x ==> y -- eta reduce
     ((#) x) ==> (x #)  -- rotate operators
     (flip op x) ==> (`op` x)  -- rotate operators
     \x y -> x + y ==> (+)  -- insert operator
@@ -22,26 +23,34 @@
 <TEST>
 f a = \x -> x + x -- f a x = x + x
 f a = \a -> a + a -- f _ a = a + a
+a = \x -> x + x -- a x = x + x
+f (Just a) = \a -> a + a -- f (Just _) a = a + a
+f (Foo a b c) = \c -> c + c -- f (Foo a b _) c = c + c
 f a = \x -> x + x where _ = test
 f (test -> a) = \x -> x + x
 f = \x -> x + x -- f x = x + x
 fun x y z = f x y z -- fun = f
 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 mr = y mr
 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 = (*) x
 f = foo (flip op x) -- (`op` x)
 f = foo (flip op x) -- @Message Use section
+f = foo (flip x y) -- (`x` y)
 foo x = bar (\ d -> search d table) -- (`search` table)
 foo x = bar (\ d -> search d table) -- @Message Avoid lambda using `infix`
 f = flip op x
 f = foo (flip (*) x) -- (* x)
+f = foo (flip (Prelude.*) x) -- (Prelude.* x)
 f = foo (flip (-) x)
 f = foo (\x y -> fun x y) -- @Warning fun
+f = foo (\x y z -> fun x y z) -- @Warning fun
+f = foo (\z -> f x $ z) -- f x
 f = foo (\x y -> x + y) -- (+)
 f = foo (\x -> x * y) -- @Suggestion (* y)
 f = foo (\x -> x # y)
@@ -56,6 +65,7 @@
 f = a b (\x -> c x d)  -- (`c` d)
 yes = \x -> a x where -- a
 yes = \x y -> op y x where -- flip op
+yes = \x y -> op z y x where -- flip (op z)
 f = \y -> nub $ reverse y where -- nub . reverse
 f = \z -> foo $ bar $ baz z where -- foo . bar . baz
 f = \z -> foo $ bar x $ baz z where -- foo . bar x . baz
@@ -69,113 +79,275 @@
 foo a b c = bar (flux ++ quux) c where flux = c
 yes = foo (\x -> Just x) -- @Warning Just
 foo = bar (\x -> (x `f`)) -- f
+foo = bar (\x -> shakeRoot </> "src" </> x)
 baz = bar (\x -> (x +)) -- (+)
+xs `withArgsFrom` args = f args
 foo = bar (\x -> case x of Y z -> z) -- \(Y z) -> z
+foo = bar (\x -> case x of [y, z] -> z) -- \[y, z] -> z
 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)
+yes = blah (\ x -> (y, x, y, u, v)) -- (y, , y, u, v)
+yes = blah (\ x -> (y, x, z+q)) -- @Note may require `{-# LANGUAGE TupleSections #-}` adding to the top of the file
 yes = blah (\ x -> (y, x, z+x))
 tmp = map (\ x -> runST $ action x)
 yes = map (\f -> dataDir </> f) dataFiles -- (dataDir </>)
 {-# LANGUAGE TypeApplications #-}; noBug545 = coerce ((<>) @[a])
 {-# LANGUAGE QuasiQuotes #-}; authOAuth2 name = authOAuth2Widget [whamlet|Login via #{name}|] name
 {-# LANGUAGE QuasiQuotes #-}; authOAuth2 = foo (\name -> authOAuth2Widget [whamlet|Login via #{name}|] name)
+f = {- generates a hint using hlint.yaml only -} map (flip (,) "a") "123"
+f = {- generates a hint using hlint.yaml only -} map ((,) "a") "123"
+f = map (\s -> MkFoo s 0 s) ["a","b","c"]
 </TEST>
 -}
 
 
 module Hint.Lambda(lambdaHint) where
 
-import Hint.Util
-import Hint.Type
+import Hint.Type (DeclHint, Idea, Note(RequiresExtension), suggest, warn, toSS, toSSA, suggestN, ideaNote, substVars, toRefactSrcSpan)
 import Util
 import Data.List.Extra
-import Data.Maybe
-import qualified Data.Set as Set
-import Refact.Types hiding (RType(Match))
+import Data.Set (Set)
+import Data.Set qualified as Set
+import Refact.Types hiding (Match)
+import Data.Generics.Uniplate.DataOnly (universe, universeBi, transformBi)
 
+import GHC.Types.Basic
+import GHC.Types.Fixity
+import GHC.Hs
+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)
+import GHC.Util.FreeVars (free, allVars, freeVars, pvars, vars, varss)
+import GHC.Util.HsExpr (allowLeftSection, allowRightSection, niceLambdaR, lambda)
+import GHC.Util.View
 
 lambdaHint :: DeclHint
-lambdaHint _ _ x = concatMap (uncurry lambdaExp) (universeParentBi x) ++ concatMap lambdaDecl (universe x)
+lambdaHint _ _ x
+    =  concatMap (uncurry lambdaExp) (universeParentBi 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)
 
+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 -> 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 rtype (toSSA o) sub tpl]
+       in [warn "Redundant lambda" (reLoc o) (gen pats origBody) refacts]
 
-lambdaDecl :: Decl_ -> [Idea]
-lambdaDecl (toFunBind -> o@(FunBind loc1 [Match _ name pats (UnGuardedRhs loc2 bod) bind]))
-    | isNothing bind, isLambda $ fromParen bod, null (universeBi pats :: [Exp_]) =
-      [warn "Redundant lambda" o (gen pats bod) [Replace Decl (toSS o) s1 t1]]
-    | length pats2 < length pats, pvars (drop (length pats2) pats) `disjoint` varss bind
-        = [warn "Eta reduce" (reform pats bod) (reform pats2 bod2)
-            [ -- Disabled, see apply-refact #3
-              -- Replace Decl (toSS $ reform pats bod) s2 t2]]
-            ]]
-        where reform p b = FunBind loc [Match an name p (UnGuardedRhs an b) Nothing]
-              loc = setSpanInfoEnd loc1 $ srcSpanEnd $ srcInfoSpan loc2
-              gen ps = uncurry reform . fromLambda . Lambda an ps
-              (finalpats, body) = fromLambda . Lambda an pats $ bod
-              (pats2, bod2) = etaReduce pats bod
-              template fps b = prettyPrint $ reform (zipWith munge ['a'..'z'] fps) (toNamed "body")
-              munge :: Char -> Pat_ -> Pat_
-              munge ident p@(PWildCard _) = p
-              munge ident p = PVar (ann p) (Ident (ann p) [ident])
-              subts fps b = ("body", toSS b) : zipWith (\x y -> ([x],y)) ['a'..'z'] (map toSS fps)
-              s1 = subts finalpats body
-              --s2 = subts pats2 bod2
-              t1 = template finalpats body
-              --t2 = template pats2 bod2
+    | 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 rtype (toSS $ reform pats origBody) sub tpl]
+          ]
+    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])}
 
-lambdaDecl _ = []
+          mkSubtsAndTpl newPats newBody = (sub, tpl)
+            where
+              (origPats, vars) = mkOrigPats (Just (rdrNameStr funName)) newPats
+              sub = ("body", toSSA newBody) : zip vars (map toSSA newPats)
+              tpl = unsafePrettyPrint (reform origPats varBody)
 
-setSpanInfoEnd ssi (line, col) = ssi{srcInfoSpan = (srcInfoSpan ssi){srcSpanEndLine=line, srcSpanEndColumn=col}}
+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 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)
 
-etaReduce :: [Pat_] -> Exp_ -> ([Pat_], Exp_)
-etaReduce ps (App _ x (Var _ (UnQual _ (Ident _ y))))
-    | ps /= [], PVar _ (Ident _ p) <- last ps, p == y, p /= "mr", y `notElem` vars x
-    , not $ any isQuasiQuote $ universe x
-    = etaReduce (init ps) x
-etaReduce ps (InfixApp a x (isDol -> True) y) = etaReduce ps (App a x y)
-etaReduce ps x = (ps,x)
+lambdaExp :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea]
+lambdaExp _ o@(L _ (HsPar _ (L _ (HsApp _ oper@(L _ (HsVar _ origf@(L _ (rdrNameOcc -> f)))) y))))
+    | isSymOcc f -- is this an operator?
+    , isAtom y
+    , allowLeftSection $ occNameString f
+    , not $ isTypeApp y
+    = [suggest "Use section" (reLoc o) (reLoc to) [r]]
+    where
+        to :: LHsExpr GhcPs
+        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" (reLoc o) (reLoc to) [r]]
+    where
+        to :: LHsExpr GhcPs
+        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 (toSSA o) [(var, toSSA y)] ("(" ++ op ++ " " ++ var ++ ")")
 
---Section refactoring is not currently implemented.
-lambdaExp :: Maybe Exp_ -> Exp_ -> [Idea]
-lambdaExp p o@(Paren _ (App _ v@(Var l (UnQual _ (Symbol _ x))) y)) | isAtom y, not $ isTypeApp y, allowLeftSection x =
-    [suggestN "Use section" o (exp y x)] -- [Replace Expr (toSS o) subts template]]
+lambdaExp p o@(L _ (HsLam _ LamSingle _))
+    | not $ any isOpApp p
+    , (res, refact) <- niceLambdaR [] o
+    , not $ isLambda 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.
+    , let from = case p of
+              -- Avoid creating redundant bracket.
+              Just p@(L _ (HsPar _ (L _ HsLam{})))
+                | L _ HsPar{} <- res -> p
+                | L _ (HsVar _ (L _ name)) <- res, not (isSymbolRdrName name) -> p
+              _ -> o
+    = [(if isVar res then warn else suggest) name (reLoc from) (reLoc res) (refact $ toSSA from)]
     where
-      exp op rhs = LeftSection an op (toNamed rhs)
---      template = prettyPrint (exp (toNamed "a") "*")
---      subts = [("a", toSS y), ("*", toSS v)]
-lambdaExp p o@(Paren _ (App _ (App _ (view -> Var_ "flip") (Var _ x)) y)) | allowRightSection $ fromNamed x =
-    [suggestN "Use section" o $ RightSection an (QVarOp an x) y]
-lambdaExp p o@Lambda{}
-    | maybe True (not . isInfixApp) p, (res, refact) <- niceLambdaR [] o
-    , not $ isLambda res, not $ any isQuasiQuote $ universe res, not $ "runST" `Set.member` freeVars o
-    , let name = "Avoid lambda" ++ (if countInfixNames res > countInfixNames o then " using `infix`" else "") =
-    [(if isVar res || isCon res then warn else suggest) name o res (refact $ toSS o)]
-    where countInfixNames x = length [() | RightSection _ (QVarOp _ (UnQual _ (Ident _ _))) _ <- universe x]
-lambdaExp p o@(Lambda _ pats x) | isLambda (fromParen x), null (universeBi pats :: [Exp_]), maybe True (not . isLambda) p =
-    [suggest "Collapse lambdas" o (Lambda an pats body) [Replace Expr (toSS o) subts template]]
+        countRightSections :: LHsExpr GhcPs -> Int
+        countRightSections x = length [() | L _ (SectionR _ (view -> Var_ _) _) <- universe x]
+
+lambdaExp p o@(SimpleLambda origPats origBody)
+    | 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" (reLoc o) (reLoc (lambda pats body)) [Replace Expr (toSSA o) subts template]]
     where
       (pats, body) = fromLambda o
-      template = prettyPrint $  Lambda an (zipWith munge ['a'..'z'] pats) (toNamed "body")
-      munge :: Char -> Pat_ -> Pat_
-      munge ident p@(PWildCard _) = p
-      munge ident p = PVar (ann p) (Ident (ann p) [ident])
-      subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) ['a'..'z'] (map toSS pats)
-lambdaExp p o@(Lambda _ [view -> PVar_ u] (Case _ (view -> Var_ v) alts))
-    | u == v, u `notElem` vars alts = case alts of
-        [Alt _ pat (UnGuardedRhs _ bod) Nothing] -> [suggestN "Use lambda" o $ Lambda an [pat] bod]
-        _ -> [(suggestN "Use lambda-case" o $ LCase an alts){ideaNote=[RequiresExtension "LambdaCase"]}]
-lambdaExp p o@(Lambda _ [view -> PVar_ u] (Tuple _ boxed xs))
-    | ([yes],no) <- partition (~= u) xs, u `notElem` concatMap vars no
-    = [(suggestN "Use tuple-section" o $ TupleSection an boxed [if x ~= u then Nothing else Just x | x <- xs])
-        {ideaNote=[RequiresExtension "TupleSections"]}]
+      (oPats, vars) = mkOrigPats Nothing 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
+lambdaExp _ o@(SimpleLambda [view -> PVar_ x] (L _ expr)) =
+    case expr of
+        -- suggest TupleSections instead of lambdas
+        ExplicitTuple _ args boxity
+            -- is there exactly one argument that is exactly x?
+            | ([_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" (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?
+            | x == x'
+            -- x must not be used in some other way inside the matches
+            , Set.notMember x $ Set.map occNameString $ free $ allVars matchGroup
+            -> case matchGroup of
+                 -- is there a single match? - suggest match inside the lambda
+                 --
+                 -- 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]))
+                   | all (\(L _ (GRHS _ stmts _)) -> null stmts) (grhssGRHSs (m_grhss oldmatch)) ->
+                     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 (toSSA o) [("x", toRefactSrcSpan xloc), ("y", toRefactSrcSpan yloc)]
+                                     ((if needParens then "\\(x)" else "\\x") ++ " -> y")
+                                 ]
+                           | otherwise = []
+                         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
+                                         }
+                                     ]
+                                 }
+                               :: Located (HsExpr GhcPs)
+                             )
+                             r
+                         ]
+
+                 -- otherwise we should use @LambdaCase@
+                 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 :: 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 :: HsTupArg GhcPs -> Maybe String
+        tupArgVar (Present _ (view -> Var_ x)) = Just x
+        tupArgVar _ = Nothing
+
 lambdaExp _ _ = []
 
+varBody :: LHsExpr GhcPs
+varBody = strToVar "body"
 
--- replace any repeated pattern variable with _
-fromLambda :: Exp_ -> ([Pat_], Exp_)
-fromLambda (Lambda _ ps1 (fromLambda . fromParen -> (ps2,x))) = (transformBi (f $ pvars ps2) ps1 ++ ps2, x)
-    where f bad x@PVar{} | prettyPrint x `elem` bad = PWildCard an
+-- | Squash lambdas and replace any repeated pattern variable with @_@
+fromLambda :: LHsExpr GhcPs -> ([LPat GhcPs], LHsExpr GhcPs)
+fromLambda (SimpleLambda ps1 (fromLambda . fromParen -> (ps2,x))) = (transformBi (f $ pvars ps2) ps1 ++ ps2, x)
+    where f :: [String] -> Pat GhcPs -> Pat GhcPs
+          f bad (VarPat _ (rdrNameStr -> x))
+              | x `elem` bad = WildPat noExtField
           f bad x = x
 fromLambda x = ([], x)
+
+-- | For each pattern, if it does not contain wildcards, replace it with a variable pattern.
+--
+-- The second component of the result is a list of substitution variables, which are guaranteed
+-- to not occur in the function name or patterns with wildcards. For example, given
+-- 'f (Foo a b _) = ...', 'f', 'a' and 'b' are not usable as substitution variables.
+mkOrigPats :: Maybe String -> [LPat GhcPs] -> ([LPat GhcPs], [String])
+mkOrigPats funName pats = (zipWith munge vars pats', vars)
+  where
+    (Set.unions -> used, pats') = unzip (map f pats)
+
+    -- Remove variables that occur in the function name or patterns with wildcards
+    vars = filter (\s -> s `Set.notMember` used && Just s /= funName) substVars
+
+    -- Returns (chars in the pattern if the pattern contains wildcards, (whether the pattern contains wildcards, the pattern))
+    f :: LPat GhcPs -> (Set String, (Bool, LPat GhcPs))
+    f p
+      | any isWildPat (universe p) =
+          let used = Set.fromList [rdrNameStr name | (L _ (VarPat _ name)) <- universe p]
+           in (used, (True, p))
+      | otherwise = (mempty, (False, p))
+
+    -- 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 (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:
@@ -32,41 +33,56 @@
 issue619 = [pkgJobs | Pkg{pkgGpd, pkgJobs} <- pkgs, not $ null $ C.condTestSuites pkgGpd]
 {-# LANGUAGE MonadComprehensions #-}\
 foo = [x | False, x <- [1 .. 10]] -- []
+foo = [_ | x <- _, let _ = A{x}]
+issue1039 = foo (map f [1 | _ <- []]) -- [f 1 | _ <- []]
+{-# LANGUAGE OverloadedLists #-} \
+issue114 = True:[]
 </TEST>
 -}
 
 module Hint.List(listHint) where
 
 import Control.Applicative
-import Data.Generics.Uniplate.Operations
+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',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 HsSyn
-import SrcLoc
-import BasicTypes
-import RdrName
-import OccName
-import Name
-import FastString
-import TysWiredIn
+import GHC.Hs
+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.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
 
-listDecl :: LHsDecl GhcPs -> [Idea]
-listDecl x =
-  concatMap (listExp False) (childrenBi x) ++
+listHint :: DeclHint
+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 :: Bool -> LHsDecl GhcPs -> [Idea]
+listDecl overloadedListsOn x =
+  concatMap (listExp overloadedListsOn False) (childrenBi x) ++
   stringType x ++
   concatMap listPat (childrenBi x) ++
   concatMap listComp (universeBi x)
@@ -75,191 +91,199 @@
 -- structure of 'listComp'.
 
 listComp :: LHsExpr GhcPs -> [Idea]
-listComp o@(LL _ (HsDo _ ListComp (L _ stmts))) =
+listComp o@(L _ (HsDo _ ListComp (L _ stmts))) =
   listCompCheckGuards o ListComp stmts
-listComp o@(LL _ (HsDo _ MonadComp (L _ stmts))) =
+listComp o@(L _ (HsDo _ MonadComp (L _ stmts))) =
   listCompCheckGuards o MonadComp stmts
-
-listComp o@(view' -> App2' mp f (LL _ (HsDo _ ListComp (L _ 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
-listComp o@(view' -> App2' mp f (LL _ (HsDo _ MonadComp (L _ stmts)))) =
+listComp o@(view -> App2 mp f (L _ (HsDo _ MonadComp (L _ stmts)))) =
   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@(LL _ 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 noExt Nothing []
-        o2 = noLoc $ HsDo noExt ctx (noLoc (filter ((/= Just "True") . qualCon) xs ++ [e]))
-        o3 = noLoc $ HsDo noExt 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 _ (LL _ (HsVar _ (L _ x))) _ _)) = Just (occNameString . rdrNameOcc $ x)
+        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
-      LL _ (LastStmt _ body b s) = head revs -- In a ListComp, this is always last.
-      last = noLoc $ LastStmt noExt (noLoc $ HsApp noExt (paren' f) (paren' body)) b s
-      o2 =noLoc $ HsDo noExt 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)
+              span (if any hasPFieldsDotDot (universeBi x)
+                       || any isPFieldWildcard (universeBi x)
                       then const False
-                      else \x -> pvars' p `disjoint` vars_ x) guards
+                      else \x ->
+                       let pvs = pvars p in
+                       -- See this code from 'RdrHsSyn.hs' (8.10.1):
+                       --   plus_RDR, pun_RDR :: RdrName
+                       --   plus_RDR = mkUnqual varName (fsLit "+") -- Hack
+                       --   pun_RDR  = mkUnqual varName (fsLit "pun-right-hand-side")
+                       -- Todo (SF, 2020-03-28): Try to make this better somehow.
+                       pvs `disjoint` varss x && "pun-right-hand-side" `notElem` pvs
+                   ) guards
     f guards (x@(L _ BodyStmt{}):xs) = f (x:guards) xs
     f guards (x@(L _ LetStmt{}):xs) = f (x:guards) xs
     f guards xs = reverse guards ++ xs
 
-    -- Fake something that works
-    vars_ x = [unsafePrettyPrint a | HsVar _ (LL _ a) <- universeBi x :: [HsExpr GhcPs]]
-
-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 :: Pat GhcPs -> [Idea]
-listPat x = if null res then concatMap listPat $ children x else [head res]
-    where res = [suggest' name x x2 [r]
+listPat :: LPat GhcPs -> [Idea]
+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 ]
-isAppend :: View' a App2' => a -> Bool
-isAppend (view' -> App2' op _ _) = varToStr op == "++"
+                  , 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 drop 1 -- 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, Pat GhcPs -> Maybe (Pat GhcPs, [(String, R.SrcSpan)], String))]
-pchecks = let (*) = (,) in drop 1 -- see #174
+pchecks :: [(String, LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String))]
+pchecks = let (*) = (,) in drop1 -- see #174
     [ "Use string literal pattern" * usePString
     , "Use list literal pattern" * usePList
     ]
 
-usePString :: Pat GhcPs -> Maybe (Pat GhcPs, [a], String)
-usePString (LL _ (ListPat _ xs)) | not $ null xs, Just s <- mapM fromPChar' xs =
-  let literal = noLoc $ LitPat noExt (HsString NoSourceText (fsLit (show s)))
+usePString :: LPat GhcPs -> Maybe (LPat GhcPs, [a], String)
+usePString (L _ (ListPat _ xs)) | not $ null xs, Just s <- mapM fromPChar xs =
+  let literal = noLocA $ LitPat noExtField (HsString NoSourceText (fsLit (show s))) :: LPat GhcPs
   in Just (literal, [], unsafePrettyPrint literal)
 usePString _ = Nothing
 
-usePList :: Pat GhcPs -> Maybe (Pat GhcPs, [(String, R.SrcSpan)], String)
+usePList :: LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String)
 usePList =
   fmap  ( (\(e, s) ->
-             (noLoc (ListPat noExt e)
-             , map (fmap toSS') s
-             , unsafePrettyPrint (noLoc $ ListPat noExt (map snd s) :: Pat GhcPs))
+             (noLocA (ListPat noAnn e)
+             , map (fmap toRefactSrcSpan . fst) s
+             , unsafePrettyPrint (noLocA $ ListPat noAnn (map snd s) :: LPat GhcPs))
           )
           . unzip
         )
-  . f True ['a'..'z']
+  . f True substVars
   where
-    f first _ x | patToStr' x == "[]" = if first then Nothing else Just []
-    f first (ident:cs) (view' -> PApp_' ":" [a, b]) = ((a, g ident a) :) <$> f False cs b
+    f first _ x | patToStr x == "[]" = if first then Nothing else Just []
+    f first (ident:cs) (view -> PApp_ ":" [a, b]) = ((a, g ident a) :) <$> f False cs b
     f first _ _ = Nothing
 
-    g :: Char -> Pat GhcPs -> (String, Pat GhcPs)
-    g c p = ([c], VarPat noExt (noLoc $ mkVarUnqual (fsLit [c])))
+    g :: String -> LPat GhcPs -> ((String, SrcSpan), LPat GhcPs)
+    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 (LL _ (ExplicitList _ _ xs)) | not $ null xs, Just s <- mapM fromChar xs =
-  let literal = noLoc (HsLit noExt (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 noExt Nothing e)
-             , map (fmap toSS') s
-             , unsafePrettyPrint (noLoc $ ExplicitList noExt Nothing (map snd s) :: LHsExpr GhcPs))
+             (noLocA (ExplicitList noAnn e)
+             , map (fmap toSSA) s
+             , unsafePrettyPrint (noLocA $ ExplicitList noAnn (map snd s) :: LHsExpr GhcPs))
           )
           . unzip
         )
-  . f True ['a'..'z']
+  . f True substVars
   where
     f first _ x | varToStr x == "[]" = if first then Nothing else Just []
-    f first (ident:cs) (view' -> App2' c a b) | varToStr c == ":" =
+    f first (ident:cs) (view -> App2 c a b) | varToStr c == ":" =
           ((a, g ident a) :) <$> f False cs b
     f first _ _ = Nothing
 
-    g :: Char -> LHsExpr GhcPs -> (String, LHsExpr GhcPs)
-    g c p = ([c], strToVar [c])
+    g :: String -> LHsExpr GhcPs -> (String, LHsExpr GhcPs)
+    g s p = (s, L (getLoc p) (unLoc $ strToVar s))
 
-useCons :: View' a App2' => Bool -> a -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String)
-useCons False (view' -> App2' op x y) | varToStr op == "++"
-                                       , Just (x2, build) <- f x
-                                       , not $ isAppend y =
-    Just (gen (build x2) y
-         , [("x", toSS' x2), ("xs", toSS' y)]
-         , unsafePrettyPrint $ gen (build $ strToVar "x") (strToVar "xs")
+useCons :: View a App2 => Bool -> a -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String)
+useCons False (view -> App2 op x y) | varToStr op == "++"
+                                    , Just (newX, tplX, spanX) <- f x
+                                    , not $ isAppend y =
+    Just (gen newX y
+         , [("x", spanX), ("xs", toSSA y)]
+         , unsafePrettyPrint $ gen tplX (strToVar "xs")
          )
   where
-    f :: LHsExpr GhcPs ->
-      Maybe (LHsExpr GhcPs, LHsExpr GhcPs -> LHsExpr GhcPs)
-    f (LL _ (ExplicitList _ _ [x]))=
-      Just (x, \v -> if isApp x then v else paren' v)
+    f :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, R.SrcSpan)
+    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 noExt x (noLoc (HsVar noExt  (noLoc consDataCon_RDR)))
+    gen x = noLocA . OpApp noExtField x (noLocA (HsVar noExtField  (noLocA consDataCon_RDR)))
 useCons _ _ = Nothing
 
 typeListChar :: LHsType GhcPs
 typeListChar =
-  noLoc $ HsListTy noExt
-    (noLoc (HsTyVar noExt NotPromoted (noLoc (mkVarUnqual (fsLit "Char")))))
+  noLocA $ HsListTy noAnn
+    (noLocA (HsTyVar noAnn NotPromoted (noLocA (mkVarUnqual (fsLit "Char")))))
 
 typeString :: LHsType GhcPs
 typeString =
-  noLoc $ HsTyVar noExt NotPromoted (noLoc (mkVarUnqual (fsLit "String")))
+  noLocA $ HsTyVar noAnn NotPromoted (noLocA (mkVarUnqual (fsLit "String")))
 
 stringType :: LHsDecl GhcPs  -> [Idea]
-stringType (LL _ x) = case x of
+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) = [suggest' "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]
-stringType _ = [] -- {-# COMPLETE LL #-}
+            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,32 +31,37 @@
 
 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.Operations
+import Data.Generics.Uniplate.DataOnly
 import Data.List.Extra
 import Data.Maybe
 import Data.Either.Extra
 import Control.Monad
 import Refact.Types hiding (RType(Match))
 
-import SrcLoc
-import HsExtension
-import HsPat
-import HsTypes
-import TysWiredIn
-import RdrName
-import HsBinds
-import HsExpr
-import HsDecls
-import OccName
-import BasicTypes
+import GHC.Types.SrcLoc
+import GHC.Hs.Extension
+import GHC.Hs.Pat
+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 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
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
-listRecHint :: DeclHint'
+listRecHint :: DeclHint
 listRecHint _ _ = concatMap f . universe
     where
         f o = maybeToList $ do
@@ -64,10 +69,10 @@
             (x, addCase) <- findCase x
             (use,severity,x) <- matchListRec x
             let y = addCase x
-            guard $ recursiveStr `notElem` varss' y
+            guard $ recursiveStr `notElem` varss y
             -- Maybe we can do better here maintaining source
             -- formatting?
-            return $ 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_"
@@ -91,7 +96,6 @@
     Int -- list position
     BList (LHsExpr GhcPs) -- list type/body
 
-
 ---------------------------------------------------------------------
 -- MATCH THE RECURSION
 
@@ -99,50 +103,50 @@
 matchListRec :: ListCase -> Maybe (String, Severity, LHsExpr GhcPs)
 matchListRec o@(ListCase vs nil (x, xs, cons))
     -- Suggest 'map'?
-    | [] <- vs, varToStr nil == "[]", (LL _ (OpApp _ lhs c rhs)) <- cons, varToStr c == ":"
-    , astEq (fromParen' rhs) recursive, xs `notElem` vars' lhs
+    | [] <- vs, varToStr nil == "[]", (L _ (OpApp _ lhs c rhs)) <- cons, varToStr c == ":"
+    , astEq (fromParen rhs) recursive, xs `notElem` vars lhs
     = Just $ (,,) "map" Hint.Type.Warning $
-      appsBracket' [ strToVar "map", niceLambda' [x] lhs, strToVar xs]
+      appsBracket [ strToVar "map", niceLambda [x] lhs, strToVar xs]
     -- Suggest 'foldr'?
-    | [] <- vs, App2' op lhs rhs <- view' cons
-    , xs `notElem` (vars' op ++ vars' lhs) -- the meaning of xs changes, see #793
-    , astEq (fromParen' rhs) recursive
+    | [] <- vs, App2 op lhs rhs <- view cons
+    , xs `notElem` (vars op ++ vars lhs) -- the meaning of xs changes, see #793
+    , astEq (fromParen rhs) recursive
     = Just $ (,,) "foldr" Suggestion $
-      appsBracket' [ strToVar "foldr", niceLambda' [x] $ appsBracket' [op,lhs], nil, strToVar xs]
+      appsBracket [ strToVar "foldr", niceLambda [x] $ appsBracket [op,lhs], nil, strToVar xs]
     -- Suggest 'foldl'?
-    | [v] <- vs, view' nil == Var_' v, (LL _ (HsApp _ r lhs)) <- cons
-    , astEq (fromParen' r) recursive
-    , xs `notElem` vars' lhs
+    | [v] <- vs, view nil == Var_ v, (L _ (HsApp _ r lhs)) <- cons
+    , astEq (fromParen r) recursive
+    , xs `notElem` vars lhs
     = Just $ (,,) "foldl" Suggestion $
-      appsBracket' [ strToVar "foldl", niceLambda' [v,x] lhs, strToVar v, strToVar xs]
+      appsBracket [ strToVar "foldl", niceLambda [v,x] lhs, strToVar v, strToVar xs]
     -- Suggest 'foldM'?
-    | [v] <- vs, (LL _ (HsApp _ ret res)) <- nil, isReturn ret, varToStr res == "()" || view' res == Var_' v
-    , [LL _ (BindStmt _ (view' -> PVar_' b1) e _ _), LL _ (BodyStmt _ (fromParen' -> (LL _ (HsApp _ r (view' -> Var_' b2)))) _ _)] <- asDo cons
-    , b1 == b2, astEq r recursive, xs `notElem` vars' e
+    | [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
+    , b1 == b2, astEq r recursive, xs `notElem` vars e
     , name <- "foldM" ++ ['_' | varToStr res == "()"]
     = Just $ (,,) name Suggestion $
-      appsBracket' [strToVar name, niceLambda' [v,x] e, strToVar v, strToVar xs]
+      appsBracket [strToVar name, niceLambda [v,x] e, strToVar v, strToVar xs]
     -- Nope, I got nothing ¯\_(ツ)_/¯.
     | otherwise = Nothing
 
 -- Very limited attempt to convert >>= to do, only useful for
 -- 'foldM' / 'foldM_'.
 asDo :: LHsExpr GhcPs -> [LStmt GhcPs (LHsExpr GhcPs)]
-asDo (view' ->
-       App2' bind lhs
-         (LL _ (HsLam _ MG {
-              mg_origin=FromSource
-            , mg_alts=LL _ [
-                 LL _ Match {  m_ctxt=LambdaExpr
-                            , m_pats=[LL _ v@VarPat{}]
+asDo (view ->
+       App2 bind lhs
+         (L _ (HsLam _ LamSingle MG {
+              mg_ext=FromSource
+            , mg_alts=L _ [
+                 L _ Match {  m_ctxt=(LamAlt LamSingle)
+                            , m_pats=L _ [v@(L _ VarPat{})]
                             , m_grhss=GRHSs _
-                                        [LL _ (GRHS _ [] rhs)]
-                                        (LL _ (EmptyLocalBinds _))}]}))
+                                        [L _ (GRHS _ [] rhs)]
+                                        (EmptyLocalBinds _)}]}))
       ) =
-  [ noLoc $ BindStmt noExt v lhs noSyntaxExpr noSyntaxExpr
-  , noLoc $ BodyStmt noExt rhs noSyntaxExpr noSyntaxExpr ]
-asDo (LL _ (HsDo _ DoExpr (LL _ stmts))) = stmts
-asDo x = [noLoc $ BodyStmt noExt 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]
 
 
 ---------------------------------------------------------------------
@@ -152,46 +156,46 @@
 findCase :: LHsDecl GhcPs -> Maybe (ListCase, LHsExpr GhcPs -> LHsDecl GhcPs)
 findCase x = do
   -- Match a function binding with two alternatives.
-  (LL _ (ValD _ FunBind {fun_matches=
-              MG{mg_origin=FromSource, mg_alts=
-                     (LL _
-                            [ x1@(LL _ Match{..}) -- Match fields.
+  (L _ (ValD _ FunBind {fun_matches=
+              MG{mg_ext=FromSource, mg_alts=
+                     (L _
+                            [ x1@(L _ Match{..}) -- Match fields.
                             , x2]), ..} -- Match group fields.
           , ..} -- Fun. bind fields.
-      )) <- return x
+      )) <- pure x
 
   Branch name1 ps1 p1 c1 b1 <- findBranch x1
   Branch name2 ps2 p2 c2 b2 <- findBranch x2
   guard (name1 == name2 && ps1 == ps2 && p1 == p2)
-  [(BNil, b1), (BCons x xs, b2)] <- return $ sortOn fst [(c1, b1), (c2, b2)]
-  b2 <- transformAppsM' (delCons name1 p1 xs) b2
-  (ps, b2) <- return $ eliminateArgs ps1 b2
+  [(BNil, b1), (BCons x xs, b2)] <- pure $ sortOn fst [(c1, b1), (c2, b2)]
+  b2 <- transformAppsM (delCons name1 p1 xs) b2
+  (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 noExt -- Empty where clause.
-      gRHS e = noLoc $ GRHS noExt [] e :: LGRHS GhcPs (LHsExpr GhcPs) -- Guarded rhs.
-      gRHSSs e = GRHSs noExt [gRHS e] emptyLocalBinds -- Guarded rhs set.
-      match e = Match{m_ext=noExt,m_pats=ps12, m_grhss=gRHSSs e, ..} -- Match.
-      matchGroup e = MG{mg_alts=noLoc [noLoc $ match e], mg_origin=Generated, ..} -- Match group.
+  let ps12 = let (a, b) = splitAt p1 ps1 in map strToPat (a ++ xs : b) -- Function arguments.
+      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.
 
-  return (ListCase ps b1 (x, xs, b2), noLoc . ValD noExt . 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
-    (pre, (view' -> Var_' v) : post) <- return $ splitAt pos xs
+delCons func pos var (fromApps -> (view -> Var_ x) : xs) | func == x = do
+    (pre, (view -> Var_ v) : post) <- pure $ splitAt pos xs
     guard $ v == var
-    return $ apps' $ recursive : pre ++ post
-delCons _ _ _ x = return x
+    pure $ apps $ recursive : pre ++ post
+delCons _ _ _ x = pure x
 
 eliminateArgs :: [String] -> LHsExpr GhcPs -> ([String], LHsExpr GhcPs)
 eliminateArgs ps cons = (remove ps, transform f cons)
   where
-    args = [zs | z : zs <- map fromApps' $ universeApps' cons, astEq z recursive]
-    elim = [all (\xs -> length xs > i && view' (xs !! i) == Var_' p) args | (i, p) <- zip [0..] ps] ++ repeat False
+    args = [zs | z : zs <- map fromApps $ universeApps cons, astEq z recursive]
+    elim = [all (\xs -> length xs > i && view (xs !! i) == Var_ p) args | (i, p) <- zipFrom 0 ps] ++ repeat False
     remove = concat . zipWith (\b x -> [x | not b]) elim
 
-    f (fromApps' -> x : xs) | astEq x recursive = apps' $ x : remove xs
+    f (fromApps -> x : xs) | astEq x recursive = apps $ x : remove xs
     f x = x
 
 
@@ -205,24 +209,24 @@
             , m_pats = ps
             , m_grhss =
               GRHSs {grhssGRHSs=[L l (GRHS _ [] body)]
-                        , grhssLocalBinds=L _ (EmptyLocalBinds _)
+                        , grhssLocalBinds=EmptyLocalBinds _
                         }
-            } <- return x
-  (a, b, c) <- findPat ps
-  return $ Branch (occNameString $rdrNameOcc name) a b c $ simplifyExp' body
+            } <- pure x
+  (a, b, c) <- findPat (unLoc ps)
+  pure $ Branch (occNameStr name) a b c $ simplifyExp body
 
 findPat :: [LPat GhcPs] -> Maybe ([String], Int, BList)
 findPat ps = do
   ps <- mapM readPat ps
-  [i] <- return $ findIndices isRight ps
+  [i] <- pure $ findIndices isRight ps
   let (left, [right]) = partitionEithers ps
 
-  return (left, i, right)
+  pure (left, i, right)
 
-readPat :: Pat GhcPs -> Maybe (Either String BList)
-readPat (view' -> PVar_' x) = Just $ Left x
-readPat (LL _ (ParPat _ (LL _ (ConPatIn (L _ n) (InfixCon (view' -> PVar_' x) (view' -> PVar_' xs))))))
+readPat :: LPat GhcPs -> Maybe (Either String BList)
+readPat (view -> PVar_ x) = Just $ Left x
+readPat (L _ (ParPat _ (L _ (ConPat _ (L _ n) (InfixCon (view -> PVar_ x) (view -> PVar_ xs))))))
  | n == consDataCon_RDR = Just $ Right $ BCons x xs
-readPat (LL _ (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,5 +1,6 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE RecordWildCards, NamedFieldPuns, TupleSections #-}
-{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables #-}
+{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts #-}
 
 {-
 The matching does a fairly simple unification between the two terms, treating
@@ -8,7 +9,6 @@
 both ($) and (.) functions on the right.
 
 TRANSFORM PATTERNS
-_eval_ - perform deep evaluation, must be used at the top of a RHS
 _noParen_ - don't bracket this particular item
 
 SIDE CONDITIONS
@@ -38,228 +38,242 @@
 not . not . x ==> x
 -}
 
-module Hint.Match(readMatch') where
+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
 import Data.Maybe
 import Config.Type
-import Data.Generics.Uniplate.Operations
+import Data.Generics.Uniplate.DataOnly
 
-import Bag
-import HsSyn
-import SrcLoc
-import BasicTypes
-import RdrName
-import OccName
+import GHC.Hs
+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
 import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
-readMatch' :: [HintRule] -> Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea]
-readMatch' settings = findIdeas' (concatMap readRule' settings)
+readMatch :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea]
+readMatch settings = findIdeas (concatMap readRule settings)
 
-readRule' :: HintRule -> [HintRule]
-readRule' m@HintRule{ hintRuleGhcLHS=(stripLocs' . unextendInstances -> hintRuleGhcLHS)
-                    , hintRuleGhcRHS=(stripLocs' . unextendInstances -> hintRuleGhcRHS)
-                    , hintRuleGhcSide=((stripLocs' . unextendInstances <$>) -> hintRuleGhcSide)
+readRule :: HintRule -> [HintRule]
+readRule m@HintRule{ hintRuleLHS=(stripLocs . unextendInstances -> hintRuleLHS)
+                    , hintRuleRHS=(stripLocs . unextendInstances -> hintRuleRHS)
+                    , hintRuleSide=((stripLocs . unextendInstances <$>) -> hintRuleSide)
                     } =
-   (:) m{ hintRuleGhcLHS=extendInstances hintRuleGhcLHS
-        , hintRuleGhcRHS=extendInstances hintRuleGhcRHS
-        , hintRuleGhcSide=extendInstances <$> hintRuleGhcSide } $ do
-    (l, v1) <- dotVersion' hintRuleGhcLHS
-    (r, v2) <- dotVersion' hintRuleGhcRHS
+   (:) m{ hintRuleLHS=extendInstances hintRuleLHS
+        , hintRuleRHS=extendInstances hintRuleRHS
+        , hintRuleSide=extendInstances <$> hintRuleSide } $ do
+    (l, v1) <- dotVersion hintRuleLHS
+    (r, v2) <- dotVersion hintRuleRHS
 
-    guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars' $ maybeToList hintRuleGhcSide ++ l ++ r))
+    guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars $ maybeToList hintRuleSide ++ l ++ r))
     if not (null r) then
-      [ m{ hintRuleGhcLHS=extendInstances (dotApps' l), hintRuleGhcRHS=extendInstances (dotApps' r), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide }
-      , m{ hintRuleGhcLHS=extendInstances (dotApps' (l ++ [strToVar v1])), hintRuleGhcRHS=extendInstances (dotApps' (r ++ [strToVar v1])), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide } ]
+      [ m{ hintRuleLHS=extendInstances (dotApps l), hintRuleRHS=extendInstances (dotApps r), hintRuleSide=extendInstances <$> hintRuleSide }
+      , m{ hintRuleLHS=extendInstances (dotApps (l ++ [strToVar v1])), hintRuleRHS=extendInstances (dotApps (r ++ [strToVar v1])), hintRuleSide=extendInstances <$> hintRuleSide } ]
       else if length l > 1 then
-            [ m{ hintRuleGhcLHS=extendInstances (dotApps' l), hintRuleGhcRHS=extendInstances (strToVar "id"), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide }
-            , m{ hintRuleGhcLHS=extendInstances (dotApps' (l++[strToVar v1])), hintRuleGhcRHS=extendInstances (strToVar v1), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide}]
+            [ m{ hintRuleLHS=extendInstances (dotApps l), hintRuleRHS=extendInstances (strToVar "id"), hintRuleSide=extendInstances <$> hintRuleSide }
+            , m{ hintRuleLHS=extendInstances (dotApps (l++[strToVar v1])), hintRuleRHS=extendInstances (strToVar v1), hintRuleSide=extendInstances <$> hintRuleSide}]
       else []
 
 -- Find a dot version of this rule, return the sequence of app
 -- prefixes, and the var.
-dotVersion' :: LHsExpr GhcPs -> [([LHsExpr GhcPs], String)]
-dotVersion' (view' -> Var_' v) | isUnifyVar v = [([], v)]
-dotVersion' (LL _ (HsApp _ ls rs)) = first (ls :) <$> dotVersion' (fromParen' rs)
-dotVersion' (LL l (OpApp _ x op y)) =
+dotVersion :: LHsExpr GhcPs -> [([LHsExpr GhcPs], String)]
+dotVersion (view -> Var_ v) | isUnifyVar v = [([], v)]
+dotVersion (L _ (HsApp _ ls rs)) = first (ls :) <$> dotVersion (fromParen rs)
+dotVersion (L l (OpApp _ x op y)) =
   -- In a GHC parse tree, raw sections aren't valid application terms.
   -- To be suitable as application terms, they must be enclosed in
   -- parentheses.
 
   --   If a == b then
   --   x is 'a', op is '==' and y is 'b' and,
-  let lSec = addParen' (cL l (SectionL noExt x op)) -- (a == )
-      rSec = addParen' (cL l (SectionR noExt op y)) -- ( == b)
-  in (first (lSec :) <$> dotVersion' y) ++ (first (rSec :) <$> dotVersion' x) -- [([(a ==)], b), ([(b == )], a])].
-dotVersion' _ = []
+  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 _ = []
 
 ---------------------------------------------------------------------
 -- PERFORM THE MATCHING
 
-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}
-    | (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)
+findIdeas :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea]
+findIdeas matches s _ decl = timed "Hint" "Match apply" $ forceList
+    [ (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 (toSSA x) subst (unsafePrettyPrint tpl)
     ]
 
 -- | A list of root expressions, with their associated names
-findDecls' :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)]
-findDecls' x@(LL _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) =
-    [(fromMaybe "" $ bindName xs, x) | xs <- bagToList cid_binds, x <- childrenBi xs]
-findDecls' (LL _ RuleD{}) = [] -- Often rules contain things that HLint would rewrite.
-findDecls' x = map (fromMaybe "" $ declName x,) $ childrenBi x
+findDecls :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)]
+findDecls x@(L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) =
+    [(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
 
-matchIdea' :: Scope'
+matchIdea :: Scope
            -> String
            -> HintRule
            -> Maybe (Int, LHsExpr GhcPs)
            -> LHsExpr GhcPs
            -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, [Note], [(String, R.SrcSpan)])
-matchIdea' sb declName HintRule{..} parent x = do
-  let lhs = unextendInstances hintRuleGhcLHS
-      rhs = unextendInstances hintRuleGhcRHS
-      sa  = unextendInstances hintRuleGhcScope
-      nm a b = scopeMatch' (sa, a) (sb, b)
-  u <- unifyExp' nm True lhs x
-  u <- validSubst' astEq u
+matchIdea sb declName HintRule{..} parent x = do
+  let lhs = unextendInstances hintRuleLHS
+      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 (e, tpl) = substitute' u rhs
-      res = addBracketTy' (addBracket' parent $ performSpecial' $ fst $ substitute' u $ unqualify' sa sb rhs)
-  guard $ (freeVars' e Set.\\ Set.filter (not . isUnifyVar . occNameString) (freeVars' rhs)) `Set.isSubsetOf` freeVars' x
+  let rhs' | Just fun <- extra = rebracket1 $ noLocA (HsApp noExtField fun rhs)
+           | otherwise = rhs
+      (e, (tpl, substNoParens)) = substitute u rhs'
+      noParens = [varToStr $ fromParen x | L _ (HsApp _ (varToStr -> "_noParen_") x) <- universe tpl]
+
+  u <- pure (removeParens noParens u)
+
+  let res = addBracketTy (addBracket parent $ performSpecial $ fst $ substitute u $ unqualify sa sb rhs')
+  guard $ (freeVars e Set.\\ Set.filter (not . isUnifyVar . occNameString) (freeVars rhs')) `Set.isSubsetOf` freeVars x
       -- Check no unexpected new free variables.
 
   -- Check it isn't going to get broken by QuasiQuotes as per #483. If
   -- 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 <$> hintRuleGhcSide) $ ("original", x) : ("result", res) : fromSubst' u
-  guard $ checkDefine' declName parent res
+  guard $ checkSide (unextendInstances <$> hintRuleSide) $ ("original", x) : ("result", res) : fromSubst u
+  guard $ checkDefine declName parent rhs
 
-  return (res, tpl, hintRuleNotes, [(s, toSS' pos) | (s, pos) <- fromSubst' u, getLoc pos /= noSrcSpan])
+  (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, toSSA pos') | (s, pos) <- fromSubst u, locA (getLoc pos) /= noSrcSpan
+                          , let pos' = if s `elem` substNoParens then fromParen pos else pos
+         ]
+       )
+
 ---------------------------------------------------------------------
 -- SIDE CONDITIONS
 
-checkSide' :: Maybe (LHsExpr GhcPs) -> [(String, LHsExpr GhcPs)] -> Bool
-checkSide' x bind = maybe True bool x
+checkSide :: Maybe (LHsExpr GhcPs) -> [(String, LHsExpr GhcPs)] -> Bool
+checkSide x bind = maybe True bool x
     where
       bool :: LHsExpr GhcPs -> Bool
-      bool (LL _ (OpApp _ x op y))
+      bool (L _ (OpApp _ x op y))
         | varToStr op == "&&" = bool x && bool y
         | varToStr op == "||" = bool x || bool y
-        | varToStr op == "==" = expr (fromParen1' x) `astEq` expr (fromParen1' y)
-      bool (LL _ (HsApp _ x y)) | varToStr x == "not" = not $ bool y
-      bool (LL _ (HsPar _ x)) = bool x
+        | varToStr op == "==" = expr (fromParen1 x) `astEq` expr (fromParen1 y)
+      bool (L _ (HsApp _ x y)) | varToStr x == "not" = not $ bool y
+      bool (L _ (HsPar _ x)) = bool x
 
-      bool (LL _ (HsApp _ cond (sub -> y)))
+      bool (L _ (HsApp _ cond (sub -> y)))
         | 'i' : 's' : typ <- varToStr cond = isType typ y
-      bool (LL _ (HsApp _ (LL _ (HsApp _ cond (sub -> x))) (sub -> y)))
-          | varToStr cond == "notIn" = and [extendInstances (stripLocs' x) `notElem` map (extendInstances . stripLocs') (universe y) | x <- list x, y <- list y]
+      bool (L _ (HsApp _ (L _ (HsApp _ cond (sub -> x))) (sub -> y)))
+          | varToStr cond == "notIn" = and [extendInstances (stripLocs x) `notElem` map (extendInstances . stripLocs) (universe y) | x <- list x, y <- list y]
           | varToStr cond == "notEq" = not (x `astEq` y)
       bool x | varToStr x == "noTypeCheck" = True
       bool x | varToStr x == "noQuickCheck" = True
-      bool x = error $ "Hint.Match.checkSide', unknown side condition: " ++ unsafePrettyPrint x
+      bool x = error $ "Hint.Match.checkSide, unknown side condition: " ++ unsafePrettyPrint x
 
       expr :: LHsExpr GhcPs -> LHsExpr GhcPs
-      expr (LL _ (HsApp _ (varToStr -> "subst") x)) = sub $ fromParen1' x
+      expr (L _ (HsApp _ (varToStr -> "subst") x)) = sub $ fromParen1 x
       expr x = x
 
       isType "Compare" x = True -- Just a hint for proof stuff
-      isType "Atom" x = isAtom' x
+      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" (LL _ (HsLit _ HsInt{})) = True
-      isType "LitInt" (LL _ (HsOverLit _ (OverLit _ HsIntegral{} _))) = True
-      isType "Var" (LL _ HsVar{}) = True
-      isType "App" (LL _ HsApp{}) = True
-      isType "InfixApp" (LL _ x@OpApp{}) = True
-      isType "Paren" (LL _ x@HsPar{}) = True
-      isType "Tuple" (LL _ ExplicitTuple{}) = True
+      isType "LitInt" (L _ (HsLit _ HsInt{})) = True
+      isType "LitInt" (L _ (HsOverLit _ (OverLit _ HsIntegral{}))) = True
+      isType "LitString" (L _ (HsLit _ HsString{})) = True
+      isType "Var" (L _ HsVar{}) = True
+      isType "App" (L _ HsApp{}) = True
+      isType "InfixApp" (L _ x@OpApp{}) = True
+      isType "Paren" (L _ x@HsPar{}) = True
+      isType "Tuple" (L _ ExplicitTuple{}) = True
 
-      isType typ (LL _ x) =
+      isType typ (L _ x) =
         let top = showConstr (toConstr x) in
         typ == top
-      isType _ _ = False -- {-# COMPLETE LL#-}
 
       asInt :: LHsExpr GhcPs -> Maybe Integer
-      asInt (LL _ (HsPar _ x)) = asInt x
-      asInt (LL _ (NegApp _ x _)) = negate <$> asInt x
-      asInt (LL _ (HsLit _ (HsInt _ (IL _ neg x)) )) = Just $ if neg then -x else x
-      asInt (LL _ (HsOverLit _ (OverLit _ (HsIntegral (IL _ neg x)) _))) = Just $ if neg then -x else x
+      asInt (L _ (HsPar _ x)) = asInt x
+      asInt (L _ (NegApp _ x _)) = negate <$> asInt 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 (LL _ (ExplicitList _ _ xs)) = xs
+      list (L _ (ExplicitList _ xs)) = xs
       list x = [x]
 
       sub :: LHsExpr GhcPs -> LHsExpr GhcPs
       sub = transform f
-        where f (view' -> Var_' x) | Just y <- lookup x bind = y
+        where f (view -> Var_ x) | Just y <- lookup x bind = y
               f x = x
 
 -- 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
-        LL _ (HsApp _ fun _) -> funOrOp fun
-        LL _ (OpApp _ _ op _) -> funOrOp op
-        other -> other
-   in declName /= varToStr (transformBi unqual' $ funOrOp y)
-checkDefine' _ _ _ = True
+checkDefine :: String -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
+checkDefine declName Nothing y =
+  let funOrOp expr = (case expr of
+        L _ (HsApp _ fun _) -> funOrOp fun
+        L _ (OpApp _ _ op _) -> funOrOp op
+        other -> other) :: LHsExpr GhcPs
+   in declName /= varToStr (transformBi unqual $ funOrOp y)
+checkDefine _ _ _ = True
 
 ---------------------------------------------------------------------
 -- TRANSFORMATION
 
--- If it has '_eval_' do evaluation on it.
-performSpecial' :: LHsExpr GhcPs -> LHsExpr GhcPs
-performSpecial' = transform fNoParen . fEval
+-- If it has '_noParen_', remove the brackets (if exist).
+performSpecial :: LHsExpr GhcPs -> LHsExpr GhcPs
+performSpecial = transform fNoParen
   where
-    fEval, fNoParen :: LHsExpr GhcPs -> LHsExpr GhcPs
-    fEval (LL _ (HsApp _ e x)) | varToStr e == "_eval_" = reduce' x
-    fEval x = x
-    fNoParen (LL _ (HsApp _ e x)) | varToStr e == "_noParen_" = fromParen' x
+    fNoParen :: LHsExpr GhcPs -> LHsExpr GhcPs
+    fNoParen (L _ (HsApp _ e x)) | varToStr e == "_noParen_" = fromParen x
     fNoParen x = x
 
 -- Contract : 'Data.List.foo' => 'foo' if 'Data.List' is loaded.
-unqualify' :: Scope' -> Scope' -> LHsExpr GhcPs -> LHsExpr GhcPs
-unqualify' from to = transformBi f
+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
+    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 noExt c
-addBracket' _ x = x
+addBracket :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs
+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
 -- need to bracket type applications in  This doesn't come up in HSE
 -- because the pretty printer inserts them.
-addBracketTy' :: LHsExpr GhcPs -> LHsExpr GhcPs
-addBracketTy'= transformBi f
+addBracketTy :: LHsExpr GhcPs -> LHsExpr GhcPs
+addBracketTy= transformBi f
   where
     f :: LHsType GhcPs -> LHsType GhcPs
-    f (LL _ (HsAppTy _ t x@(LL _ HsAppTy{}))) =
-      noLoc (HsAppTy noExt t (noLoc (HsParTy noExt x)))
+    f (L _ (HsAppTy _ t x@(L _ HsAppTy{}))) =
+      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 ViewPatterns, PatternGuards, FlexibleContexts #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns #-}
+
 {-
     Find and match:
 
@@ -13,7 +20,7 @@
 yes = do _ <- mapM print a; return b -- mapM_ print a
 no = mapM print a
 no = do foo ; mapM print a
-yes = do (bar+foo) -- (bar+foo)
+yes = do (bar+foo) --
 no = do bar ; foo
 yes = do bar; a <- foo; return a -- do bar; foo
 no = do bar; a <- foo; return b
@@ -27,8 +34,8 @@
 yes = do x <- bar $ baz; return (f $ g x)
 no = do x <- bar; return (f x x)
 {-# LANGUAGE RecursiveDo #-}; no = mdo hook <- mkTrigger pat (act >> rmHook hook) ; return hook
-yes = do x <- return y; foo x -- @Suggestion do let x = y; foo x
-yes = do x <- return $ y + z; foo x -- do let x = y + z; foo x
+yes = do x <- return y; foo x -- @Suggestion let x = y
+yes = do x <- return $ y + z; foo x -- let x = y + z
 no = do x <- return x; foo x
 no = do x <- return y; x <- return y; foo x
 yes = do forM files $ \x -> return (); return () -- forM_ files $ \x -> return ()
@@ -39,93 +46,174 @@
 folder f a xs = foldM f a xs >>= \_ -> return () -- foldM_ f a xs
 yes = mapM async ds >>= mapM wait >> return () -- mapM async ds >>= mapM_ wait
 main = "wait" ~> do f a $ sleep 10
-main = print do 17 + 25
-main = print do 17 -- 17
-main = f $ do g a $ sleep 10 -- g a $ sleep 10
-main = do f a $ sleep 10 -- f a $ sleep 10
-main = do foo x; return 3; bar z -- do foo x; bar z
+{-# LANGUAGE BlockArguments #-}; main = print do 17 + 25
+{-# LANGUAGE BlockArguments #-}; main = print do 17 --
+main = f $ do g a $ sleep 10 --
+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
+bar = 1 * do {\x -> x+x} + y
+issue978 = do \
+   print "x" \
+   if False then main else do \
+   return ()
 </TEST>
 -}
 
 
 module Hint.Monad(monadHint) where
 
-import Hint.Type(DeclHint',Idea,ideaNote,warn',toSS',suggest',Note(Note))
+import Hint.Type
 
-import HsSyn
-import SrcLoc
-import BasicTypes
-import TcEvidence
-import RdrName
-import OccName
-import Bag
+import GHC.Hs hiding (Warning)
+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
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 import GHC.Util
 
+import Data.Generics.Uniplate.DataOnly
 import Data.Tuple.Extra
 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]
 badFuncs = ["mapM","foldM","forM","replicateM","sequence","zipWithM","traverse","for","sequenceA"]
 unitFuncs :: [String]
 unitFuncs = ["when","unless","void"]
 
-monadHint :: DeclHint'
-monadHint _ _ d = concatMap (monadExp d) $ universeParentExp' d
+monadHint :: DeclHint
+monadHint _ _ d = concatMap (f Nothing Nothing) $ childrenBi d
+    where
+        decl = declName d
+        f parentDo parentExpr x =
+            monadExp decl parentDo parentExpr x ++
+            concat [f (if isHsDo x then Just x else parentDo) (Just (i, x)) c | (i, c) <- zipFrom 0 $ children x]
 
-monadExp :: LHsDecl GhcPs -> (Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs) -> [Idea]
-monadExp (declName -> decl) (parent, x) =
+        isHsDo (L _ HsDo{}) = True
+        isHsDo _ = False
+
+
+-- | Call with the name of the declaration,
+--   the nearest enclosing `do` expression
+--   the nearest enclosing expression
+--   the expression of interest
+monadExp :: Maybe String -> Maybe (LHsExpr GhcPs) -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea]
+monadExp decl parentDo parentExpr x =
   case x of
-    (view' -> App2' op x1 x2) | isTag ">>" op -> f x1
-    (view' -> App2' op x1 (view' -> LamConst1' _)) | isTag ">>=" op -> f x1
-    (LL l (HsApp _ op x)) | isTag "void" op -> seenVoid (cL l . HsApp noExt op) x
-    (LL l (OpApp _ op dol x)) | isTag "void" op, isDol dol -> seenVoid (cL l . OpApp noExt op dol) x
-    (LL loc (HsDo _ _ (LL _ [LL _ (BodyStmt _ y _ _ )]))) -> [warn' "Redundant do" x y [Replace Expr (toSS' x) [("y", toSS' y)] "y"] | not $ doOperator parent y]
-    (LL loc (HsDo _ DoExpr (L _ xs))) ->
-      monadSteps (cL loc . HsDo noExt DoExpr . noLoc) xs ++
-      [suggest' "Use let" x (cL loc (HsDo noExt DoExpr (noLoc y)) :: LHsExpr GhcPs) rs | Just (y, rs) <- [monadLet xs]] ++
-      concat [f x | (LL _ (BodyStmt _ x _ _)) <- init xs] ++
-      concat [f x | (LL _ (BindStmt _ (LL _ WildPat{}) x _ _)) <- init xs]
+    (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 (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 (locA loc)) doOrMDo [Replace Expr (toSSA x) [("y", toSSA y)] "y"]
+          | not $ doAsBrackets parentExpr y
+          , not $ doAsAvoidingIndentation parentDo x
+          ]
+    (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 _ (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 [] | 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 _ ->
+        let start = realSrcSpanStart s
+            end = mkRealSrcLoc (srcSpanFile s) (srcLocLine start) (srcLocCol start + length doOrMDo)
+         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'
-doOperator :: (Eq a, Num a) => Maybe (a, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
-doOperator (Just (2, LL _ (OpApp _ _ op _ )))  (LL _ OpApp {}) | not $ isDol op = True
-doOperator (Just (1, LL _ HsApp{})) b | not $ isAtom' b = True
-doOperator _ _ = False
+-- Sometimes people write 'a * do a + b', to avoid brackets,
+-- or using BlockArguments they can write 'a do a b',
+-- or using indentation a * do {\b -> c} * d
+-- Return True if they are using do as brackets
+doAsBrackets :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
+doAsBrackets (Just (2, L _ (OpApp _ _ op _ ))) _ | isDol op = False -- not quite atomic, but close enough
+doAsBrackets (Just (i, o)) x = needBracket i o x
+doAsBrackets Nothing x = False
 
+
+-- Sometimes people write do, to avoid indentation, see
+-- https://github.com/ndmitchell/hlint/issues/978
+-- Return True if they are using do as avoiding indentation
+doAsAvoidingIndentation :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool
+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 (LL _ (HsPar _ x)) = returnsUnit x
-returnsUnit (LL _ (HsApp _ x _)) = returnsUnit x
-returnsUnit (LL _ (OpApp _ x op _)) | isDol op = returnsUnit x
-returnsUnit (LL _ (HsVar _ (L _ x))) = occNameString (rdrNameOcc 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 (LL l (HsPar _ x)) = monadNoResult inside (wrap . cL l . HsPar noExt) x
-monadNoResult inside wrap (LL l (HsApp _ x y)) = monadNoResult inside (\x -> wrap $ cL l (HsApp noExt x y)) x
-monadNoResult inside wrap (LL l (OpApp _ x tag@(LL _ (HsVar _ (L _ op))) y))
-    | isDol tag = monadNoResult inside (\x -> wrap $ cL l (OpApp noExt x tag y)) x
-    | occNameString (rdrNameOcc op) == ">>=" = monadNoResult inside (wrap . cL l . OpApp noExt x tag) y
+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 $ 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]
-monadNoResult inside wrap (replaceBranches' -> (bs, rewrap)) =
+    = [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]
 
@@ -133,55 +221,54 @@
            -> [ExprLStmt GhcPs] -> [Idea]
 
 -- Rewrite 'do return x; $2' as 'do $2'.
-monadStep wrap os@(o@(LL _ (BodyStmt _ (fromRet -> Just (ret, _)) _ _ )) : xs@(_:_))
-  = [warn' ("Redundant " ++ ret) (wrap os) (wrap xs) [Delete Stmt (toSS' o)]]
+monadStep wrap (o@(L _ (BodyStmt _ (fromRet -> Just (ret, _)) _ _ )) : xs@(_:_))
+  = [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@(LL _ (BindStmt _ (LL _ (VarPat _ (L _ p))) x _ _ ))
-                  , q@(LL _ (BodyStmt _ (fromRet -> Just (ret, LL _ (HsVar _ (L _ v)))) _ _))]
-  | occNameString (rdrNameOcc p) == occNameString (rdrNameOcc v)
-  = [warn' ("Redundant " ++ ret) (wrap o) (wrap [noLoc $ BodyStmt noExt x noSyntaxExpr noSyntaxExpr])
-      [Replace Stmt (toSS' g) [("x", toSS' x)] "x", Delete Stmt (toSS' q)]]
+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) (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@(LL _ (BindStmt _ (view' -> PVar_' p) x _ _)):q@(LL _ (BodyStmt _ (view' -> Var_' v) _ _)):xs)
-  | p == v && v `notElem` varss' xs
-  = let app = noLoc $ HsApp noExt (strToVar "join") x
-        body = noLoc $ BodyStmt noExt (rebracket1' app) noSyntaxExpr noSyntaxExpr
+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 = 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@(LL loc (BindStmt _ p x _ _)) : rest)
-    | isPWildCard' p, returnsUnit x
-    = let body = cL loc $ BodyStmt noExt x noSyntaxExpr noSyntaxExpr :: ExprLStmt GhcPs
-      in [warn' "Redundant variable capture" o body []]
+monadStep wrap (o@(L loc (BindStmt _ p x)) : rest)
+    | isPWildcard p, returnsUnit 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@[ LL _ (BodyStmt _ x _ _)
-         , LL _ (BodyStmt _ (fromRet -> Just (ret, LL _ (HsVar _ (L _ unit)))) _ _)]
-     | returnsUnit x, occNameString (rdrNameOcc unit) == "()"
-  = [warn' ("Redundant " ++ ret) (wrap o) (wrap $ take 1 o) []]
+  wrap o@[ L _ (BodyStmt _ x _ _)
+         , q@(L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ unit)))) _ _))]
+     | returnsUnit x, occNameStr unit == "()"
+  = [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@(LL _ (BindStmt _ (view' -> PVar_' u) x _ _))
-    , q@(LL _ (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)
+  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 noExt (noLoc $ OpApp noExt (foldl' (\acc e -> noLoc $ OpApp noExt 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)
+    isSimple (fromApps -> xs) = all isAtom (x : xs)
     vs = ('f':) . show <$> [0..]
 
     notDol :: LHsExpr GhcPs -> Bool
-    notDol (LL _ (OpApp _ _ op _)) = not $ isDol op
+    notDol (L _ (OpApp _ _ op _)) = not $ isDol op
     notDol _ = True
-
 monadStep _ _ = []
 
 -- Suggest removing a return
@@ -190,40 +277,39 @@
 monadSteps _ _ = []
 
 -- | Rewrite 'do ...; x <- return y; ...' as 'do ...; let x = y; ...'.
-monadLet :: [ExprLStmt GhcPs] -> Maybe ([ExprLStmt GhcPs], [Refactoring R.SrcSpan])
-monadLet xs = if null rs then Nothing else Just (ys, rs)
+monadLet :: [ExprLStmt GhcPs] -> [(ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan)]
+monadLet xs = mapMaybe mkLet xs
   where
-    (ys, catMaybes -> rs) = unzip $ map mkLet xs
-    vs = concatMap pvars' [p | (LL _ (BindStmt _ p _ _ _)) <- xs]
+    vs = concatMap pvars [p | (L _ (BindStmt _ p _ )) <- xs]
 
-    mkLet :: ExprLStmt GhcPs -> (ExprLStmt GhcPs, Maybe (Refactoring R.SrcSpan))
-    mkLet g@(LL _ (BindStmt _ v@(view' -> PVar_' p) (fromRet -> Just (_, y)) _ _ ))
-      | p `notElem` vars' y, p `notElem` delete p vs
-      = (template p y, Just refact)
+    mkLet :: ExprLStmt GhcPs -> Maybe (ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan)
+    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' g) [("lhs", toSS' v), ("rhs", toSS' y)]
+        refact = Replace Stmt (toSSA x) [("lhs", toSSA v), ("rhs", toSSA y)]
                       (unsafePrettyPrint $ template "lhs" (strToVar "rhs"))
-    mkLet x = (x, Nothing)
+    mkLet _ = Nothing
 
     template :: String -> LHsExpr GhcPs -> ExprLStmt GhcPs
     template lhs rhs =
-        let p = noLoc $ mkRdrUnqual (mkVarOcc lhs)
-            grhs = noLoc (GRHS noExt [] rhs)
-            grhss = GRHSs noExt [grhs] (noLoc (EmptyLocalBinds noExt))
-            match = noLoc $ Match noExt (FunRhs p Prefix NoSrcStrict) [] grhss
-            fb = noLoc $ FunBind noExt p (MG noExt (noLoc [match]) Generated) WpHole []
-            binds = unitBag fb
-            valBinds = ValBinds noExt binds []
-            localBinds = noLoc $ HsValBinds noExt valBinds
-         in noLoc $ LetStmt noExt 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 (LL _ (HsApp _ f x)) = first (f:) $ fromApplies (fromParen' x)
-fromApplies (LL _ (OpApp _ f (isDol -> True) x)) = first (f:) $ fromApplies x
+fromApplies (L _ (HsApp _ f x)) = first (f:) $ fromApplies (fromParen x)
+fromApplies (L _ (OpApp _ f (isDol -> True) x)) = first (f:) $ fromApplies x
 fromApplies x = ([], x)
 
 fromRet :: LHsExpr GhcPs -> Maybe (String, LHsExpr GhcPs)
-fromRet (LL _ (HsPar _ x)) = fromRet x
-fromRet (LL _ (OpApp _ x (LL _ (HsVar _ (L _ y))) z)) | occNameString (rdrNameOcc y) == "$" = fromRet $ noLoc (HsApp noExt x z)
-fromRet (LL _ (HsApp _ x y)) | isReturn x = Just (unsafePrettyPrint x, y)
+fromRet (L _ (HsPar _ x)) = fromRet x
+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 #-}
 {-
@@ -15,7 +16,7 @@
     Don't suggest for FFI, since they match their C names
 
 <TEST>
-data Yes = Foo | Bar'Test -- data Yes = Foo | BarTest
+data Yes = Foo | Bar'Test
 data Yes = Bar | Test_Bar -- data Yes = Bar | TestBar
 data No = a :::: b
 data Yes = Foo {bar_cap :: Int}
@@ -25,7 +26,7 @@
 no = 1 where yes_foo = 2
 a -== b = 1
 myTest = 1; my_test = 1
-semiring'laws = 1 -- semiringLaws = ...
+semiring'laws = 1
 data Yes = FOO_A | Foo_B -- data Yes = FOO_A | FooB
 case_foo = 1
 test_foo = 1
@@ -41,40 +42,44 @@
 
 module Hint.Naming(namingHint) where
 
-import Hint.Type (Idea,DeclHint',suggest',isSym,toSrcSpan',ghcModule)
-import Data.Generics.Uniplate.Operations
+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 Refact.Types hiding (RType(Match))
-import qualified Data.Set as Set
+import Data.Set qualified as Set
 
-import BasicTypes
-import FastString
-import HsDecls
-import HsExtension
-import HsSyn
-import OccName
-import SrcLoc
+import GHC.Types.Basic
+import GHC.Types.SourceText
+import GHC.Data.FastString
+import GHC.Hs.Decls
+import GHC.Hs.Extension
+import GHC.Hs
+import GHC.Types.Name.Occurrence
+import GHC.Types.SrcLoc
 
+import Language.Haskell.GhclibParserEx.GHC.Hs.Decls
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 import GHC.Util
 
-namingHint :: DeclHint'
+namingHint :: DeclHint
 namingHint _ modu = naming $ Set.fromList $ concatMap getNames $ hsmodDecls $ unLoc (ghcModule modu)
 
 naming :: Set.Set String -> LHsDecl GhcPs -> [Idea]
 naming seen originalDecl =
-    [ suggest' "Use camelCase"
-               (shorten originalDecl)
-               (shorten replacedDecl)
-               [Replace Bind (toSrcSpan' originalDecl) [] (unsafePrettyPrint replacedDecl)]
+    [ suggest "Use camelCase"
+               (reLoc (shorten originalDecl))
+               (reLoc (shorten replacedDecl))
+               [ -- https://github.com/mpickering/apply-refact/issues/39
+               ]
     | not $ null suggestedNames
     ]
     where
         suggestedNames =
             [ (originalName, suggestedName)
-            | not $ isForD' originalDecl
+            | not $ isForD originalDecl
             , originalName <- nubOrd $ getNames originalDecl
             , Just suggestedName <- [suggestName originalName]
             , not $ suggestedName `Set.member` seen
@@ -82,40 +87,50 @@
         replacedDecl = replaceNames suggestedNames originalDecl
 
 shorten :: LHsDecl GhcPs -> LHsDecl GhcPs
-shorten (LL locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG _ (LL locMatches matches) FromSource) _ _))) =
-    LL locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = LL locMatches $ map shortenMatch matches}})
-shorten (LL locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _) _))) =
-    LL locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}})
+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 _)))) =
+    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 (LL locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) =
-    LL locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}}
-shortenMatch x = x
+shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) =
+    L locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}}
 
 shortenLGRHS :: LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs)
-shortenLGRHS (LL locGRHS (GRHS ttg0 guards (LL locExpr _))) =
-    LL locGRHS (GRHS ttg0 guards (cL locExpr dots))
+shortenLGRHS (L locGRHS (GRHS ttg0 guards (L locExpr _))) =
+    L locGRHS (GRHS ttg0 guards (L locExpr dots))
     where
         dots :: HsExpr GhcPs
-        dots = HsLit NoExt (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
+
 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 '#' $ drp '\'' $ 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
@@ -11,59 +11,75 @@
 data Foo a b = Foo a -- newtype Foo a b = Foo a
 data Foo = Foo { field1, field2 :: Int}
 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 S a = forall b . Show b => S b
+{-# 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#
+data A = A (MutableByteArray# s)
 {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn (# Ann, x #)
 {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn {getWithAnn :: (# Ann, x #)}
 data A = A () -- newtype A = A ()
 newtype Foo = Foo Int deriving (Show, Eq) --
 newtype Foo = Foo { getFoo :: Int } deriving (Show, Eq) --
 newtype Foo = Foo Int deriving stock Show
+data instance Foo Int = Bar Bool -- newtype instance Foo Int = Bar Bool
+data instance Foo Int = Bar {field :: Bool} -- newtype instance Foo Int = Bar {field :: Bool}
+data instance Foo Int = Bar {field :: Int#}
+data instance Foo Int = Bar
+data instance Foo Int = Bar {field1 :: Bool, field2 :: ()}
+newtype instance Foo Int = Bar Bool deriving (Show, Eq) --
+newtype instance Foo Int = Bar {field :: Bool} deriving Show --
+newtype instance Foo Int = Bar {field :: Bool} deriving stock Show
+{-# LANGUAGE RankNTypes #-}; data instance Foo Int = forall a. Show a => Foo a
 </TEST>
 -}
 module Hint.NewType (newtypeHint) where
 
-import Hint.Type (Idea, DeclHint', Note(DecreasesLaziness), ideaNote, ignoreNoSuggestion', suggestN')
+import Hint.Type (Idea, DeclHint, Note(DecreasesLaziness), ideaNote, ignoreNoSuggestion, suggestN)
 
 import Data.List (isSuffixOf)
-import HsDecls
-import HsSyn
-import Outputable
-import SrcLoc
+import GHC.Hs.Decls
+import GHC.Hs
+import GHC.Types.SrcLoc
+import Data.Generics.Uniplate.Data
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
-newtypeHint :: DeclHint'
+newtypeHint :: DeclHint
 newtypeHint _ _ x = newtypeHintDecl x ++ newTypeDerivingStrategiesHintDecl x
 
 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@(LL _ (TyClD _ (DataDecl _ _ _ _ dataDef))) =
-    [ignoreNoSuggestion' "Use DerivingStrategies" decl | not $ isData dataDef, not $ hasAllStrategies dataDef]
+newTypeDerivingStrategiesHintDecl decl@(L _ (TyClD _ (DataDecl _ _ _ _ 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.
+shouldSuggestStrategies :: HsDataDefn GhcPs -> Bool
+shouldSuggestStrategies dataDef = not (isData dataDef) && not (hasAllStrategies dataDef)
+
 hasAllStrategies :: HsDataDefn GhcPs -> Bool
-hasAllStrategies (HsDataDefn _ NewType _ _ _ _ (LL _ 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 (LL _ (HsDerivingClause _ (Just _) _)) = True
+hasStrategyClause (L _ (HsDerivingClause _ (Just _) _)) = True
 hasStrategyClause _ = False
 
 data WarnNewtype = WarnNewtype
@@ -79,26 +95,52 @@
 -- * Single record field constructors get newtyped - @data X = X {getX :: Int}@ -> @newtype X = X {getX :: Int}@
 -- * All other declarations are ignored.
 singleSimpleField :: LHsDecl GhcPs -> Maybe WarnNewtype
-singleSimpleField (LL loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef@(HsDataDefn _ DataType _ _ _ [LL _ constructor] _))))
-    | Just inType <- simpleCons constructor =
-        Just WarnNewtype
-              { newDecl = LL loc $ TyClD ext decl {tcdDataDefn = dataDef
-                  { dd_ND = NewType
-                  , dd_cons = map (\(LL consloc x) -> LL consloc $ dropConsBang x) $ dd_cons dataDef
-                  }}
+singleSimpleField (L loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef)))
+    | Just inType <- simpleHsDataDefn dataDef =
+        case dropBangs dataDef of
+          DataTypeCons False [con] ->
+            Just WarnNewtype
+              { newDecl = L loc $ TyClD ext decl {tcdDataDefn = dataDef
+                  { dd_cons = NewTypeCon con }}
               , insideType = inType
               }
+          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 =
+        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
 
--- | Checks whether its argument is a \"simple constructor\" (see criteria in 'singleSimpleFieldNew')
+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 single thing under its
+-- constructor if it is.
+simpleHsDataDefn :: HsDataDefn GhcPs -> Maybe (HsType GhcPs)
+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 [LL _ inType]) _)
+simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [] [HsScaled _ (L _ inType)]) _)
     | emptyOrNoContext context
     , not $ isUnboxedTuple inType
     , not $ isHashy inType
     = Just inType
-simpleCons (ConDeclH98 _ _ _ [] context (RecCon (LL _ [LL _ (ConDeclField _ [_] (LL _ inType) _)])) _)
+simpleCons (ConDeclH98 _ _ _ [] context (RecCon (L _ [L _ (ConDeclField _ [_] (L _ inType) _)])) _)
     | emptyOrNoContext context
     , not $ isUnboxedTuple inType
     , not $ isHashy inType
@@ -106,32 +148,33 @@
 simpleCons _ = Nothing
 
 isHashy :: HsType GhcPs -> Bool
-isHashy (HsTyVar _ _ identifier) = "#" `isSuffixOf` showSDocUnsafe (ppr identifier)
-isHashy _ = False
+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
 emptyOrNoContext Nothing = True
-emptyOrNoContext (Just (LL _ [])) = True
+emptyOrNoContext (Just (L _ [])) = True
 emptyOrNoContext _ = False
 
 -- | 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}
-dropConsBang decl@(ConDeclH98 _ _ _ _ _ (RecCon (LL recloc conDeclFields)) _) =
-    decl {con_args = RecCon $ cL recloc $ removeUnpacksRecords conDeclFields}
+-- 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 $ L recloc $ removeUnpacksRecords conDeclFields}
     where
         removeUnpacksRecords :: [LConDeclField GhcPs] -> [LConDeclField GhcPs]
-        removeUnpacksRecords = map (\(LL conDeclFieldLoc x) -> LL conDeclFieldLoc $ removeConDeclFieldUnpacks x)
+        removeUnpacksRecords = map (\(L conDeclFieldLoc x) -> L conDeclFieldLoc $ removeConDeclFieldUnpacks x)
 
         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,4 +1,5 @@
-{-# LANGUAGE ViewPatterns, PatternGuards #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE ViewPatterns, PatternGuards, TypeFamilies #-}
 
 {-
     Improve the structure of code
@@ -16,7 +17,7 @@
       | c <- f b = c
 foo x = yes x x where yes x y = if a then b else if c then d else e -- yes x y ; | a = b ; | c = d ; | otherwise = e
 foo x | otherwise = y -- foo x = y
-foo x = x + x where -- foo x = x + x
+foo x = x + x where --
 foo x | a = b | True = d -- foo x | a = b ; | otherwise = d
 foo (Bar _ _ _ _) = x -- Bar{}
 foo (Bar _ x _ _) = x
@@ -25,71 +26,81 @@
 foo = case v of v -> x -- x
 foo = case v of z -> z
 foo = case v of _ | False -> x
+foo x | x < -2 * 3 = 4
 foo = case v of !True -> x -- True
-foo = case v of !(Just x) -> x -- (Just x)
-foo = case v of !(x : xs) -> x -- (x:xs)
-foo = case v of !1 -> x -- 1
-foo = case v of !x -> x
-foo = case v of !(I# x) -> y -- (I# x)
+{-# LANGUAGE BangPatterns #-}; foo = case v of !True -> x -- True
+{-# LANGUAGE BangPatterns #-}; foo = case v of !(Just x) -> x -- (Just x)
+{-# LANGUAGE BangPatterns #-}; foo = case v of !(x : xs) -> x -- (x:xs)
+{-# LANGUAGE BangPatterns #-}; foo = case v of !1 -> x -- 1
+{-# LANGUAGE BangPatterns #-}; foo = case v of !x -> x
+{-# LANGUAGE BangPatterns #-}; foo = case v of !(I# x) -> y -- (I# x)
 foo = let ~x = 1 in y -- x
 foo = let ~(x:xs) = y in z
-foo = let !x = undefined in y
-foo = let !(I# x) = 4 in x
-foo = let !(Just x) = Nothing in 3
-foo = 1 where f !False = 2 -- False
-foo = 1 where !False = True
-foo = 1 where g (Just !True) = Nothing -- True
-foo = 1 where Just !True = Nothing
+{-# LANGUAGE BangPatterns #-}; foo = let !x = undefined in y
+{-# LANGUAGE BangPatterns #-}; foo = let !(I# x) = 4 in x
+{-# LANGUAGE BangPatterns #-}; foo = let !(Just x) = Nothing in 3
+{-# LANGUAGE BangPatterns #-}; foo = 1 where f !False = 2 -- False
+{-# LANGUAGE BangPatterns #-}; foo = 1 where !False = True
+{-# LANGUAGE BangPatterns #-}; foo = 1 where g (Just !True) = Nothing -- True
+{-# LANGUAGE BangPatterns #-}; foo = 1 where Just !True = Nothing
 foo otherwise = 1 -- _
 foo ~x = y -- x
 {-# LANGUAGE Strict #-} foo ~x = y
-foo !(x, y) = x -- (x, y)
-foo ![x] = x -- [x]
+{-# LANGUAGE BangPatterns #-}; foo !(x, y) = x -- (x, y)
+{-# LANGUAGE BangPatterns #-}; foo ![x] = x -- [x]
 foo !Bar { bar = x } = x -- Bar { bar = x }
-l !(() :: ()) = x -- (() :: ())
+{-# LANGUAGE BangPatterns #-}; l !(() :: ()) = x -- (() :: ())
 foo x@_ = x -- x
 foo x@Foo = x
+otherwise = True
 </TEST>
 -}
 
 
 module Hint.Pattern(patternHint) where
 
-import Hint.Type(DeclHint',Idea,ghcAnnotations,ideaTo,toSS',toRefactSrcSpan,ghcSpanToHSE,suggest',warn')
-import Data.Generics.Uniplate.Operations
+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
 import Data.Tuple
 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 HsSyn
-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
 import Language.Haskell.GhclibParserEx.GHC.Hs.Expr
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
 
-patternHint :: DeclHint'
+patternHint :: DeclHint
 patternHint _scope modu x =
     concatMap (uncurry hints . swap) (asPattern x) ++
     -- PatBind (used in 'let' and 'where') contains lazy-by-default
     -- patterns, everything else is strict.
-    concatMap (patHint strict False) (located [p | PatBind _ p _ _ <- universeBi x :: [HsBind GhcPs]]) ++
-    concatMap (patHint strict True) (located (universeBi $ transformBi noPatBind x)) ++
+    concatMap (patHint strict False) [p | PatBind _ p _ _ <- universeBi x :: [HsBind GhcPs]] ++
+    concatMap (patHint strict True) (universeBi $ transformBi noPatBind x) ++
     concatMap expHint (universeBi x)
   where
-    located ps = [p | p@XPat{} <- ps]  -- restrict attention to patterns with locs
-    exts = nubOrd $ concatMap snd (langExts (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 (LL loc a@PatBind{}) = cL loc a{pat_lhs=noLoc (WildPat noExt)}
+    noPatBind (L loc a@PatBind{}) = L loc a{pat_lhs=noLocA (WildPat noExtField)}
     noPatBind x = x
 
 {-
@@ -105,17 +116,17 @@
 -}
 
 hints :: (String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) -> Pattern -> [Idea]
-hints gen (Pattern l rtype pat (GRHSs _ [LL _ (GRHS _ [] bod)] bind))
-  | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GRHSs noExt guards bind)) [refactoring]]
+hints gen (Pattern l rtype pat (GRHSs _ [L _ (GRHS _ [] bod)] bind))
+  | 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 noExt [noLoc $ BodyStmt noExt 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
 
@@ -123,8 +134,7 @@
       -- Check if the expression has been injected or is natural.
       zipWith checkLoc ps ['1' .. '9']
       where
-        checkLoc p@(LL l _) v = if l == noSrcSpan then Left p else Right (c ++ [v], toSS' p)
-        checkLoc _ v = undefined -- {-# COMPLETE LL #-}
+        checkLoc p@(L l _) v = if locA l == noSrcSpan then Left p else Right (c ++ [v], toSSA p)
 
     patSubts =
       case pat of
@@ -133,105 +143,108 @@
         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
+    toString' (Right (v, _)) = strToPat v
 
-    template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString' patSubts) (GRHSs noExt 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$ ghcSpanToHSE l) (f patSubts ++ f guardSubts ++ f exprSubts) template
-hints gen (Pattern l t pats o@(GRHSs _ [LL _ (GRHS _ [test] bod)] bind))
+    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 noExt [] bod)]}) [Delete Stmt (toSS' test)]]
-hints gen (Pattern l t pats bod@(GRHSs _ _ binds)) | f binds
-  = [gen "Redundant where" (Pattern l t pats bod{grhssLocalBinds=noLoc (EmptyLocalBinds noExt)}) []]
+  = [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 (LL _ (HsValBinds _ (ValBinds _ bag _))) = isEmptyBag bag
-    f (LL _ (HsIPBinds _ (IPBinds _ l))) = null l
+    f :: HsLocalBinds GhcPs -> Bool
+    f (HsValBinds _ (ValBinds _ l _)) = null l
+    f (HsIPBinds _ (IPBinds _ l)) = null l
     f _ = False
-hints gen (Pattern l t pats o@(GRHSs _ (unsnoc -> Just (gs, LL _ (GRHS _ [test] bod))) binds))
+    whereSpan = case l of
+      UnhelpfulSpan s -> UnhelpfulSpan s
+      RealSrcSpan s _ ->
+        let end = realSrcSpanEnd s
+            start = mkRealSrcLoc (srcSpanFile s) (srcLocLine end) (srcLocCol end - 5)
+         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 tag = noLoc (mkRdrUnqual $ mkVarOcc "otherwise")
-        otherwise_ = noLoc $ BodyStmt noExt (noLoc (HsVar noExt tag)) noSyntaxExpr noSyntaxExpr in
-      [gen "Use otherwise" (Pattern l t pats o{grhssGRHSs = gs ++ [noLoc (GRHS noExt [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 (LL _ (HsPar _ x)) = asGuards x
-asGuards (LL _ (HsIf _ _ a b c)) = (a, b) : asGuards c
-asGuards x = [(noLoc (HsVar noExt (noLoc (mkRdrUnqual $ mkVarOcc "otherwise"))), x)]
+asGuards (L _ (HsPar _ x)) = asGuards x
+asGuards (L _ (HsIf _ a b c)) = (a, b) : asGuards c
+asGuards x = [(strToVar "otherwise", x)]
 
-data Pattern = Pattern SrcSpan R.RType [Pat GhcPs] (GRHSs GhcPs (LHsExpr GhcPs))
+data Pattern = Pattern SrcSpan R.RType [LPat GhcPs] (GRHSs GhcPs (LHsExpr GhcPs))
 
 -- Invariant: Number of patterns may not change
 asPattern :: LHsDecl GhcPs  -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)]
-asPattern (LL loc x) = concatMap decl (universeBi x)
+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 (cL loc o :: LHsBind GhcPs) (noLoc (PatBind noExt pat rhs ([], [])) :: LHsBind GhcPs) rs)]
-    decl (FunBind _ _ (MG _ (LL _ 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@(LL loc (Match _ ctx pats grhss)) = (Pattern loc R.Match pats grhss, \msg (Pattern _ _ pats grhss) rs -> suggest' msg o (noLoc (Match noExt ctx  pats grhss) :: LMatch GhcPs (LHsExpr GhcPs)) rs)
-    match _ = undefined -- {-# COMPLETE LL #-}
-asPattern _ = [] -- {-# COMPLETE LL #-}
+    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 -> Pat GhcPs -> [Idea]
-patHint _ _ o@(LL _ (ConPatIn name (PrefixCon args)))
-  | length args >= 3 && all isPWildCard' args =
-  let rec_fields = HsRecFields [] Nothing :: HsRecFields GhcPs (Pat GhcPs)
-      new        = ConPatIn name (RecCon rec_fields) :: Pat GhcPs
+patHint :: Bool -> Bool -> LPat GhcPs -> [Idea]
+patHint _ _ o@(L _ (ConPat _ name (PrefixCon _ args)))
+  | length args >= 3 && all isPWildcard args =
+  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)]]
-patHint _ _ o@(LL _ (VarPat _ (L _ name)))
+  [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 noExt) :: Pat GhcPs) []]
-patHint lang strict o@(LL _ (BangPat _ (LL _ x)))
-  | strict, f x = [warn' "Redundant bang pattern" o x [r]]
+    [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" (reLoc o) (noLoc x :: Located (Pat GhcPs)) [r]]
   where
     f :: Pat GhcPs -> Bool
-    f (ParPat _ (LL _ x)) = f x
-    f (AsPat _ _ (LL _ x)) = f x
+    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 _ (LL _ p) _) = f p
+    f (SigPat _ (L _ p) _) = f p
     f _ = False
-    r = Replace R.Pattern (toSS' o) [("x", toSS' x)] "x"
-patHint False _ o@(LL _ (LazyPat _ (LL _ x)))
-  | f x = [warn' "Redundant irrefutable pattern" o x [r]]
+    r = Replace R.Pattern (toSSA o) [("x", toSSA pat)] "x"
+patHint False _ o@(L _ (LazyPat _ pat@(L _ x)))
+  | f x = [warn "Redundant irrefutable pattern" (reLoc o) (noLoc x :: Located (Pat GhcPs)) [r]]
   where
     f :: Pat GhcPs -> Bool
-    f (ParPat _ (LL _ x)) = f x
-    f (AsPat _ _ (LL _ x)) = f x
+    f (ParPat _ (L _ x)) = f x
+    f (AsPat _ _ (L _ x)) = f x
     f WildPat{} = True
     f VarPat{} = True
     f _ = False
-    r = Replace R.Pattern (toSS' o) [("x", toSS' x)] "x"
-patHint _ _ o@(LL _ (AsPat _ v (LL _ (WildPat _)))) =
-  [warn' "Redundant as-pattern" o v []]
+    r = Replace R.Pattern (toSSA o) [("x", toSSA pat)] "x"
+patHint _ _ o@(L _ (AsPat _ v (L _ (WildPat _)))) =
+  [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@(LL _ (HsCase _ _ (MG _ (L _ [LL _ (Match _ CaseAlt [LL _ (WildPat _)] (GRHSs _ [LL _ (GRHS _ [] e)] (LL  _ (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@(LL _ (HsCase _ (LL _ (HsVar _ (L _ x))) (MG _ (L _ [LL _ (Match _ CaseAlt [LL _ (VarPat _ (L _ y))] (GRHSs _ [LL _ (GRHS _ [] e)] (LL  _ (EmptyLocalBinds _)))) ]) FromSource )))
-  | occNameString (rdrNameOcc x) == occNameString (rdrNameOcc y) =
-      [suggest' "Redundant case" o e [r]]
+    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" (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 #-}
 
@@ -14,45 +15,53 @@
 {-# OPTIONS     -cpp #-} -- {-# LANGUAGE CPP #-}
 {-# OPTIONS_YHC -cpp #-}
 {-# OPTIONS_GHC -XFoo #-} -- {-# LANGUAGE Foo #-}
-{-# OPTIONS_GHC -fglasgow-exts #-} -- ???
+{-# OPTIONS_GHC -fglasgow-exts #-} -- ??? @NoRefactor: refactor output has one LANGUAGE pragma per extension, while hlint suggestion has a single LANGUAGE pragma
+{-# LANGUAGE RebindableSyntax, EmptyCase, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase #-}
 {-# LANGUAGE RebindableSyntax, EmptyCase, DuplicateRecordFields, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase, DuplicateRecordFields #-}
 {-# LANGUAGE RebindableSyntax #-}
-{-# OPTIONS_GHC -cpp -foo #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -foo #-}
+{-# OPTIONS_GHC -cpp -foo #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -foo #-} @NoRefactor -foo is not a valid flag
+{-# OPTIONS_GHC -cpp -w #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -w #-}
 {-# OPTIONS_GHC -cpp #-} \
 {-# LANGUAGE CPP, Text #-} --
 {-# 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',prettyExtension,glasgowExts)
+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 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 = langExts ps in
+      lang = languagePragmas ps in
     languageDupes lang ++ optToPragma opts lang
 
-optToPragma :: [(Located AnnotationComment, [String])]
-             -> [(Located AnnotationComment, [String])]
+optToPragma :: [(LEpaComment, [String])]
+             -> [(LEpaComment, [String])]
              -> [Idea]
-optToPragma flags langExts =
+optToPragma flags languagePragmas =
   [pragmaIdea (OptionsToComment (fst <$> old2) ys rs) | Just old2 <- [NE.nonEmpty old]]
   where
       (old, new, ns, rs) =
@@ -60,47 +69,51 @@
                | old <- flags, Just (new, ns) <- [optToLanguage old ls]
                , let r = mkRefact old new ns]
 
-      ls = concatMap snd langExts
+      ls = concatMap snd languagePragmas
       ns2 = nubOrd (concat ns) \\ ls
 
-      ys = [mkLangExts 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 (mkLangExts noSrcSpan [n])) ns
-        in ModifyComment (toSS' (fst old)) (intercalate "\n" (filter (not . null) (new : ns')))
+      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
-          mkFewer = rawIdea' Hint.Type.Warning "Use fewer LANGUAGE pragmas"
-          mkLanguage = rawIdea' Hint.Type.Warning "Use LANGUAGE pragmas"
+          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 ( (a@(LL l _), les) : cs ) =
+languageDupes :: [(LEpaComment, [String])] -> [Idea]
+languageDupes ( (a@(L l _), les) : cs ) =
   (if nubOrd les /= les
-       then [pragmaIdea (SingleComment a (mkLangExts l $ nubOrd les))]
-       else [pragmaIdea (MultiComment a b (mkLangExts l (nubOrd $ les ++ les'))) | ( b@(LL _ _), les' ) <- cs, not $ null $ intersect les les']
+       then [pragmaIdea (SingleComment a (mkLanguagePragmas l $ nubOrd les))]
+       else [pragmaIdea (MultiComment a b (mkLanguagePragmas l (nubOrd $ les ++ les'))) | ( b@(L _ _), les' ) <- cs, not $ disjoint les les']
   ) ++ languageDupes cs
 languageDupes _ = []
 
@@ -108,7 +121,7 @@
 strToLanguage :: String -> Maybe [String]
 strToLanguage "-cpp" = Just ["CPP"]
 strToLanguage x | "-X" `isPrefixOf` x = Just [drop 2 x]
-strToLanguage "-fglasgow-exts" = Just $ map prettyExtension glasgowExts
+strToLanguage "-fglasgow-exts" = Just $ map show glasgowExtsFlags
 strToLanguage _ = Nothing
 
 -- In 'optToLanguage p langexts', 'p' is an 'OPTIONS_GHC' pragma,
@@ -123,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])
-optToLanguage (LL loc _, flags) langExts
+               -> 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` langExts)) (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,7 +1,8 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE MultiWayIf #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE ViewPatterns #-}
 
 module Hint.Restrict(restrictHint) where
 
@@ -11,67 +12,116 @@
 foo = unsafePerformIO --
 foo = bar `unsafePerformIO` baz --
 module Util where otherFunc = unsafePerformIO $ print 1 --
-module Util where exitMessageImpure = unsafePerformIO $ print 1
+module Util where exitMessageImpure = System.IO.Unsafe.unsafePerformIO $ print 1
 foo = unsafePerformOI
+import Data.List.NonEmpty as NE \
+foo = NE.nub (NE.fromList [1, 2, 3]) --
+import Hypothetical.Module \
+foo = nub s
 </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.Operations
-import qualified Data.Set as Set
-import qualified Data.Map as Map
-import Data.List
+import Data.Generics.Uniplate.DataOnly
+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 HsSyn
-import RdrName
-import ApiAnnotation
-import Module
-import SrcLoc
-import OccName
+import GHC.Hs
+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
 
 -- 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 = langExts ps in
-    checkPragmas modu opts exts restrict ++
-    maybe [] (checkImports modu $ hsmodImports (unLoc (ghcModule m))) (Map.lookup RestrictModule restrict) ++
-    maybe [] (checkFunctions modu $ hsmodDecls (unLoc (ghcModule m))) (Map.lookup RestrictFunction restrict)
+        exts = languagePragmas ps in
+    checkPragmas modu opts exts rOthers ++
+    maybe [] (checkImports modu $ hsmodImports (unLoc (ghcModule m))) (Map.lookup RestrictModule rOthers) ++
+    checkFunctions scope modu (hsmodDecls (unLoc (ghcModule m))) rFunction
     where
         modu = modName (ghcModule m)
-        restrict = restrictions settings
+        (rFunction, rOthers) = restrictions settings
 
 ---------------------------------------------------------------------
 -- UTILITIES
 
 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)
-instance Monoid RestrictItem where
-    mempty = RestrictItem [] [] [] Nothing
-    mappend = (<>)
+    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)
 
-restrictions :: [Setting] -> Map.Map RestrictType (Bool, Map.Map String RestrictItem)
-restrictions settings = Map.map f $ Map.fromListWith (++) [(restrictType x, [x]) | SettingRestrict x <- settings]
+-- Contains a map from module (Nothing if the rule is unqualified) to (within, message), so that we can
+-- distinguish functions with the same name.
+-- For example, this allows us to have separate rules for "Data.Map.fromList" and "Data.Set.fromList".
+-- Using newtype rather than type because we want to define (<>) as 'Map.unionWith (<>)'.
+newtype RestrictFunction = RestrictFun (Map.Map (Maybe String) ([(String, String)], Maybe String))
+
+instance Semigroup RestrictFunction where
+    RestrictFun m1 <> RestrictFun m2 = RestrictFun (Map.unionWith (<>) m1 m2)
+
+type RestrictFunctions = (Bool, Map.Map String RestrictFunction)
+type OtherRestrictItems = Map.Map RestrictType (Bool, Map.Map String RestrictItem)
+
+restrictions :: [Setting] -> (RestrictFunctions, OtherRestrictItems)
+restrictions settings = (rFunction, rOthers)
     where
-        f rs = (all restrictDefault rs
-               ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin restrictBadIdents restrictMessage) | Restrict{..} <- rs, s <- restrictName])
+        (map snd -> rfs, ros) = partition ((== RestrictFunction) . fst) [(restrictType x, x) | SettingRestrict x <- settings]
+        rFunction = (all restrictDefault rfs, Map.fromListWith (<>) [mkRf s r | r <- rfs, s <- restrictName r])
+        mkRf s Restrict{..} = (name, RestrictFun $ Map.singleton modu (restrictWithin, restrictMessage))
+          where
+            -- Parse module and name from s. module = Nothing if the rule is unqualified.
+            (modu, name) = first (fmap NonEmpty.init . NonEmpty.nonEmpty) (breakEnd (== '.') s)
 
+        rOthers = Map.map f $ Map.fromListWith (++) (map (second pure) ros)
+        f rs = (all restrictDefault rs
+               ,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]}
@@ -83,61 +133,135 @@
 noteMayBreak :: Note
 noteMayBreak = Note "may break the code"
 
-within :: String -> String -> RestrictItem -> Bool
-within modu func RestrictItem{..} = any (\(a,b) -> (a == modu || a == "") && (b == func || b == "")) riWithin
+within :: String -> String -> [(String, String)] -> Bool
+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 "") $ Map.lookup x mp
+   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@(LL _ ImportDecl {..}) <- imp
-    , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (moduleNameString (unLoc ideclName)) mp
-    , let allowImport = within modu "" ri
-    , 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
@@ -145,12 +269,27 @@
     fromId (Orig _ n) = Just $ occNameString n
     fromId (Exact _)  = Nothing
 
-checkFunctions :: String -> [LHsDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea]
-checkFunctions modu decls (def, mp) =
-    [ (ideaMessage riMessage $ ideaNoTo $ warn' "Avoid restricted function" x x []){ideaDecl = [dname]}
+checkFunctions :: Scope -> String -> [LHsDecl GhcPs] -> RestrictFunctions -> [Idea]
+checkFunctions scope modu decls (def, mp) =
+    [ (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]
-    , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (occNameString (rdrNameOcc (unLoc x))) mp
-    , not $ within modu dname ri
+    , 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
     ]
+
+-- Returns Just iff there are rules for x, which are either unqualified, or qualified with a module that is
+-- one of x's possible modules.
+-- If there are multiple matching rules (e.g., there's both an unqualified version and a qualified version), their
+-- withins and messages are concatenated with (<>).
+findFunction
+    :: Map.Map String RestrictFunction
+    -> LocatedN RdrName
+    -> [ModuleName]
+    -> Maybe ([(String, String)], Maybe String)
+findFunction restrictMap (rdrNameStr -> x) (map moduleNameString -> possMods) = do
+    (RestrictFun mp) <- Map.lookup x restrictMap
+    n <- NonEmpty.nonEmpty . Map.elems $ Map.filterWithKey (const . maybe True (`elem` possMods)) mp
+    pure (sconcat n)
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 }}]
@@ -78,20 +76,19 @@
 </TEST>
 -}
 
-import Hint.Type(ModuHint,ModuleEx(..),DeclHint',Idea(..),rawIdea',warn')
+import Hint.Type(ModuHint,ModuleEx(..),DeclHint,Idea(..),rawIdea,warn)
 import Config.Type
 
-import Data.Generics.Uniplate.Operations
+import Data.Generics.Uniplate.DataOnly
 import Data.List.Extra
-import qualified Data.Map as Map
+import Data.Map qualified as Map
 
-import BasicTypes
-import HsSyn
-import RdrName
-import Outputable
-import Bag
-import SrcLoc
-import GHC.Util
+import GHC.Utils.Outputable
+import GHC.Types.Basic
+import GHC.Hs
+import GHC.Types.SrcLoc
+import GHC.Types.Name.Reader
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 smellModuleHint :: [Setting] -> ModuHint
 smellModuleHint settings scope m =
@@ -99,15 +96,15 @@
       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 [] [] ]
+             in [rawIdea Config.Type.Warning "Many imports" span displayImports  Nothing [] [] ]
       where
         f :: LImportDecl GhcPs -> String
         f = trimStart . unsafePrettyPrint
     _ -> []
 
-smellHint :: [Setting] -> DeclHint'
+smellHint :: [Setting] -> DeclHint
 smellHint settings scope m d =
   sniff smellLongFunctions SmellLongFunctions ++
   sniff smellLongTypeLists SmellLongTypeLists ++
@@ -130,10 +127,10 @@
 -- right hand sides?)
 declSpans :: LHsDecl GhcPs -> [(SrcSpan, Idea)]
 declSpans
-   (LL _ (ValD _
+   (L _ (ValD _
      FunBind {fun_matches=MG {
-                   mg_origin=FromSource
-                 , mg_alts=(LL _ [LL _ Match {
+                   mg_ext=FromSource
+                 , mg_alts=(L _ [L _ Match {
                        m_ctxt=ctx
                      , m_grhss=GRHSs{grhssGRHSs=[locGrhs]
                                  , grhssLocalBinds=where_}}])}})) =
@@ -141,43 +138,42 @@
  -- the where clause.
  rhsSpans ctx locGrhs ++ whereSpans where_
 -- Any other kind of function.
-declSpans f@(LL 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 _ (LL _ (GRHS _ _ (LL _ RecordCon {}))) = [] -- record constructors get a pass
-rhsSpans ctx (LL _ r@(GRHS _ _ (LL l _))) =
-  [(l, rawIdea' Config.Type.Warning "Long function" l (showSDocUnsafe (pprGRHS ctx r)) Nothing [] [])]
-rhsSpans _ _ = []
+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 _))) =
+  [(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 (LL l (HsValBinds _ (ValBinds _ bs _))) =
-  concatMap (declSpans . (\(LL loc bind) -> LL loc (ValD noExt 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@(LL _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (LL _ 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@(LL _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (LL _ 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/Type.hs b/src/Hint/Type.hs
--- a/src/Hint/Type.hs
+++ b/src/Hint/Type.hs
@@ -1,41 +1,38 @@
 
 module Hint.Type(
-    DeclHint, DeclHint', ModuHint, CrossHint, Hint(..),
+    DeclHint, ModuHint, CrossHint, Hint(..),
     module Export
     ) where
 
 import Data.Semigroup
 import Config.Type
-import HSE.All  as Export
+import GHC.All  as Export
 import Idea     as Export
 import Prelude
 import Refact   as Export
-import HsExtension
-import HsDecls
+import GHC.Hs.Extension
+import GHC.Hs.Decls
 import GHC.Util.Scope
 
-type DeclHint = Scope -> ModuleEx -> Decl_ -> [Idea]
-type DeclHint' = Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea]
+type DeclHint = Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea]
 type ModuHint = Scope -> ModuleEx -> [Idea]
 type CrossHint = [(Scope, ModuleEx)] -> [Idea]
 
 -- | Functions to generate hints, combined using the 'Monoid' instance.
-data Hint {- PUBLIC -} = Hint
+data Hint = Hint
     { hintModules :: [Setting] -> [(Scope, ModuleEx)] -> [Idea] -- ^ Given a list of modules (and their scope information) generate some 'Idea's.
     , hintModule :: [Setting] -> Scope -> ModuleEx -> [Idea] -- ^ Given a single module and its scope information generate some 'Idea's.
-    , hintDecl :: [Setting] -> Scope -> ModuleEx -> Decl SrcSpanInfo -> [Idea]
-    , hintDecl' :: [Setting] -> Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea]
+    , hintDecl :: [Setting] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea]
         -- ^ Given a declaration (with a module and scope) generate some 'Idea's.
         --   This function will be partially applied with one module/scope, then used on multiple 'Decl' values.
     }
 
 instance Semigroup Hint where
-    Hint x1 x2 x3 x4 <> Hint y1 y2 y3 y4 = Hint
+    Hint x1 x2 x3 <> Hint y1 y2 y3 = Hint
         (\a b -> x1 a b ++ y1 a b)
         (\a b c -> x2 a b c ++ y2 a b c)
         (\a b c d -> x3 a b c d ++ y3 a b c d)
-        (\a b c d -> x4 a b c d ++ y4 a b c d)
 
 instance Monoid Hint where
-    mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) (\_ _ _ _ -> [])
+    mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> [])
     mappend = (<>)
diff --git a/src/Hint/Unsafe.hs b/src/Hint/Unsafe.hs
--- a/src/Hint/Unsafe.hs
+++ b/src/Hint/Unsafe.hs
@@ -3,34 +3,35 @@
     Find things that are unsafe
 
 <TEST>
-{-# NOINLINE slaves #-}; slaves = unsafePerformIO newIO
-slaves = unsafePerformIO Multimap.newIO -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO Multimap.newIO
-slaves = unsafePerformIO $ f y where foo = 1 -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO $ f y where foo = 1
-slaves v = unsafePerformIO $ Multimap.newIO where foo = 1
-slaves v = x where x = unsafePerformIO $ Multimap.newIO
-slaves = x where x = unsafePerformIO $ Multimap.newIO -- {-# NOINLINE slaves #-} ; slaves = x where x = unsafePerformIO $ Multimap.newIO
-slaves = unsafePerformIO . bar
-slaves = unsafePerformIO . baz $ x -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO . baz $ x
-slaves = unsafePerformIO . baz $ x -- {-# NOINLINE slaves #-} ; slaves = unsafePerformIO . baz $ x
+{-# NOINLINE entries #-}; entries = unsafePerformIO newIO
+entries = unsafePerformIO Multimap.newIO -- {-# NOINLINE entries #-} ; entries = unsafePerformIO Multimap.newIO
+entries = unsafePerformIO $ f y where foo = 1 -- {-# NOINLINE entries #-} ; entries = unsafePerformIO $ f y where foo = 1
+entries v = unsafePerformIO $ Multimap.newIO where foo = 1
+entries v = x where x = unsafePerformIO $ Multimap.newIO
+entries = x where x = unsafePerformIO $ Multimap.newIO -- {-# NOINLINE entries #-} ; entries = x where x = unsafePerformIO $ Multimap.newIO
+entries = unsafePerformIO . bar
+entries = unsafePerformIO . baz $ x -- {-# NOINLINE entries #-} ; entries = unsafePerformIO . baz $ x
+entries = unsafePerformIO . baz $ x -- {-# NOINLINE entries #-} ; entries = unsafePerformIO . baz $ x
 </TEST>
 -}
 
 
 module Hint.Unsafe(unsafeHint) where
 
-import Hint.Type(DeclHint',ModuleEx(..),Severity(..),rawIdea',toSS')
-import Data.Char
+import Hint.Type(DeclHint,ModuleEx(..),Severity(..),rawIdea,toSSA)
+import Data.List.Extra
 import Refact.Types hiding(Match)
-import Data.Generics.Uniplate.Operations
+import Data.Generics.Uniplate.DataOnly
 
-import HsSyn
-import OccName
-import RdrName
-import FastString
-import BasicTypes
-import SrcLoc
+import GHC.Hs
+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 GHC.Util
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 -- The conditions on which to fire this hint are subtle. We are
 -- interested exclusively in application constants involving
@@ -44,43 +45,46 @@
 --   f = g where g = unsafePerformIO Multimap.newIO
 -- @
 -- 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 :: DeclHint
+unsafeHint _ (ModuleEx (L _ m)) = \ld@(L loc d) ->
+  [rawIdea Hint.Type.Warning "Missing NOINLINE pragma" (locA loc)
          (unsafePrettyPrint d)
-         (Just $ dropWhile isSpace (unsafePrettyPrint $ gen x) ++ "\n" ++ unsafePrettyPrint d)
-         [] [InsertComment (toSS' (L loc d)) (unsafePrettyPrint $ gen x)]
+         (Just $ trimStart (unsafePrettyPrint $ gen x) ++ "\n" ++ unsafePrettyPrint d)
+         [] [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 noExt (InlineSig noExt (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 | LL _(SigD _ (InlineSig _ (L _ (Unqual q))
-                                                (InlinePragma _ NoInline Nothing NeverActive FunLike))
+    noinline = [q | L _(SigD _ (InlineSig _ (L _ (Unqual q))
+                                                (InlinePragma _ (NoInline (SourceText noInline)) Nothing NeverActive FunLike))
         ) <- hsmodDecls m]
 
 isUnsafeDecl :: HsDecl GhcPs -> Bool
-isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_origin=FromSource,mg_alts=LL _ alts}}) =
+isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_ext=FromSource,mg_alts=L _ alts}}) =
   any isUnsafeApp (childrenBi alts) || any isUnsafeDecl (childrenBi alts)
 isUnsafeDecl _ = False
 
 -- Am I equivalent to @unsafePerformIO x@?
 isUnsafeApp :: HsExpr GhcPs -> Bool
-isUnsafeApp (OpApp _ (LL _ l) op _ ) | isDol op = isUnsafeFun l
-isUnsafeApp (HsApp _ (LL _ x) _) = isUnsafeFun x
+isUnsafeApp (OpApp _ (L _ l) op _ ) | isDol op = isUnsafeFun l
+isUnsafeApp (HsApp _ (L _ x) _) = isUnsafeFun x
 isUnsafeApp _ = False
 
 -- Am I equivalent to @unsafePerformIO . x@?
 isUnsafeFun :: HsExpr GhcPs -> Bool
-isUnsafeFun (HsVar _ (LL _ x)) | x == mkVarUnqual (fsLit "unsafePerformIO") = True
-isUnsafeFun (OpApp _ (LL _ l) op _) | isDot op = isUnsafeFun l
+isUnsafeFun (HsVar _ (L _ x)) | x == mkVarUnqual (fsLit "unsafePerformIO") = True
+isUnsafeFun (OpApp _ (L _ l) op _) | isDot op = isUnsafeFun l
 isUnsafeFun _ = False
diff --git a/src/Hint/Util.hs b/src/Hint/Util.hs
deleted file mode 100644
--- a/src/Hint/Util.hs
+++ /dev/null
@@ -1,95 +0,0 @@
-{-# LANGUAGE PatternGuards, ViewPatterns #-}
-
-module Hint.Util(niceLambdaR) where
-
-import HSE.All
-import Data.List.Extra
-import Refact.Types
-import Refact
-import qualified Refact.Types as R (SrcSpan)
-
--- | Generate a lambda, but prettier (if possible).
---   Generally no lambda is good, but removing just some arguments isn't so useful.
-niceLambdaR :: [String] -> Exp_ -> (Exp_, R.SrcSpan -> [Refactoring R.SrcSpan])
-
--- \xs -> (e) ==> \xs -> e
-niceLambdaR xs (Paren l x) = niceLambdaR xs x
-
--- \xs -> \v vs -> e ==> \xs v -> \vs -> e
--- \xs -> \ -> e ==> \xs -> e
-niceLambdaR xs (Lambda _ ((view -> PVar_ v):vs) x) | v `notElem` xs = niceLambdaR (xs++[v]) (Lambda an vs x)
-niceLambdaR xs (Lambda _ [] x) = niceLambdaR xs x
-
--- \ -> e ==> e
-niceLambdaR [] x = (x, const [])
-
--- \vs v -> e $ v ==> \vs -> e
-niceLambdaR (unsnoc -> Just (vs, v)) (InfixApp _ e (isDol -> True) (view -> Var_ v2))
-    | v == v2, vars e `disjoint` [v]
-    = niceLambdaR vs e
-
--- \xs -> e xs ==> e
-niceLambdaR xs (fromAppsWithLoc -> e) | map view xs2 == map Var_ xs, vars e2 `disjoint` xs, not $ null e2 =
-    (apps e2, \s -> [Replace Expr s [("x", pos)] "x"])
-    where (e',xs') = splitAt (length e - length xs) e
-          (e2, xs2) = (map fst e', map fst xs')
-          pos      = toRefactSrcSpan . srcInfoSpan $ snd (last e')
-
--- \x y -> x + y ==> (+)
-niceLambdaR [x,y] (InfixApp _ (view -> Var_ x1) (opExp -> op) (view -> Var_ y1))
-    | x == x1, y == y1, vars op `disjoint` [x,y] = (op, \s -> [Replace Expr s [] (prettyPrint op)])
-
--- \x -> x + a ==> (+ a) [heuristic, ab must be a single lexeme, or gets too complex]
-niceLambdaR [x] (view -> App2 (expOp -> Just op) xx a)
-    | isLexeme a, view xx == Var_ x, x `notElem` vars a, allowRightSection (fromNamed op) =
-      let e = rebracket1 $ RightSection an op a
-      in (e, \s -> [Replace Expr s [] (prettyPrint e)])
-
--- \x -> a + x ==> (a +) [heuristic, a must be a single lexeme, or gets too complex]
-niceLambdaR [x] (view -> App2 (expOp -> Just op) a xx)
-    | isLexeme a, view xx == Var_ x, x `notElem` vars a =
-      let e = rebracket1 $ LeftSection an a op
-      in (e, \s -> [Replace Expr s [] (prettyPrint e)])
-
--- \x y -> f y x = flip f
-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)] (prettyPrint $ gen (toNamed "x"))])
-    where
-      gen = App an (toNamed "flip")
-
--- \x -> f (b x) ==> f . b
--- \x -> f $ b x ==> f . b
-niceLambdaR [x] y | Just (z, subts) <- factor y, x `notElem` vars z = (z, \s -> [mkRefact subts s])
-    where
-        -- factor the expression with respect to x
-        factor y@(App _ ini lst) | view lst == Var_ x = Just (ini, [ann ini])
-        factor y@(App _ ini lst) | Just (z, ss) <- factor lst = let r = niceDotApp ini z
-                                                           in if r == z then Just (r, ss)
-                                                                        else Just (r, ann ini : ss)
-        factor (InfixApp _ y op (factor -> Just (z, ss))) | isDol op = let r = niceDotApp y z
-                                                                 in if r == z then Just (r, ss)
-                                                                              else Just (r, ann y : ss)
-        factor (Paren _ y@App{}) = factor y
-        factor _ = Nothing
-        mkRefact :: [S] -> R.SrcSpan -> Refactoring R.SrcSpan
-        mkRefact subts s =
-          let tempSubts = zipWith (\a b -> ([a], toRefactSrcSpan $ srcInfoSpan b)) ['a' .. 'z'] subts
-              template = dotApps (map (toNamed . fst) tempSubts)
-          in Replace Expr s tempSubts (prettyPrint template)
-
-
--- \x -> (x +) ==> (+)
--- Section handling is not yet supported for refactoring
-niceLambdaR [x] (LeftSection _ (view -> Var_ x1) op) | x == x1 =
-  let e = opExp op
-  in (e, \s -> [Replace Expr s [] (prettyPrint e)])
-
--- base case
-niceLambdaR ps x = (Lambda an (map toNamed ps) x, const [])
-
-
-
--- ($) . b ==> b
-niceDotApp :: Exp_ -> Exp_ -> Exp_
-niceDotApp a b | a ~= "$" = b
-               | otherwise = dotApp a b
diff --git a/src/HsColour.hs b/src/HsColour.hs
--- a/src/HsColour.hs
+++ b/src/HsColour.hs
@@ -3,24 +3,22 @@
 
 #ifdef GPL_SCARES_ME
 
-hsColourConsole :: IO (String -> String)
-hsColourConsole = return id
+hsColourConsole :: String -> String
+hsColourConsole = id
 
 hsColourHTML :: String -> String
 hsColourHTML = id
 
 #else
 
-import Data.Functor
 import Prelude
 
 import Language.Haskell.HsColour.TTY as TTY
 import Language.Haskell.HsColour.Colourise
 import Language.Haskell.HsColour.CSS as CSS
 
-
-hsColourConsole :: IO (String -> String)
-hsColourConsole = TTY.hscolour <$> readColourPrefs
+hsColourConsole :: String -> String
+hsColourConsole = TTY.hscolour defaultColourPrefs
 
 hsColourHTML :: String -> String
 hsColourHTML = CSS.hscolour False 1
diff --git a/src/Idea.hs b/src/Idea.hs
--- a/src/Idea.hs
+++ b/src/Idea.hs
@@ -1,26 +1,27 @@
+{-# LANGUAGE ImportQualifiedPost #-}
 {-# LANGUAGE RecordWildCards, NoMonomorphismRestriction #-}
 
 module Idea(
     Idea(..),
-    rawIdea, rawIdea', idea, idea', suggest, suggest', warn, warn',ignore, ignore',
-    rawIdeaN, rawIdeaN', suggestN, suggestN', ignoreN, ignoreN', ignoreNoSuggestion',
-    showIdeasJson, showANSI,
+    rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore,
+    rawIdeaN, suggestN, ignoreNoSuggestion,
+    showIdeasJson, showIdeaANSI,
     Note(..), showNotes,
     Severity(..),
     ) where
 
-import Data.Functor
 import Data.List.Extra
-import HSE.All
 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 qualified SrcLoc as GHC
-import qualified Outputable
-import qualified GHC.Util as GHC
+import GHC.Types.SrcLoc
+import GHC.Utils.Outputable
+import GHC.Util
 
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
+
 -- | An idea suggest by a 'Hint'.
 data Idea = Idea
     {ideaModule :: [String] -- ^ The modules the idea is for, usually a singleton.
@@ -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
@@ -45,9 +46,9 @@
     ,("severity", str $ show ideaSeverity)
     ,("hint", str ideaHint)
     ,("file", str srcSpanFilename)
-    ,("startLine", show srcSpanStartLine)
+    ,("startLine", show srcSpanStartLine')
     ,("startColumn", show srcSpanStartColumn)
-    ,("endLine", show srcSpanEndLine)
+    ,("endLine", show srcSpanEndLine')
     ,("endColumn", show srcSpanEndColumn)
     ,("from", str ideaFrom)
     ,("to", maybe "null" str ideaTo)
@@ -59,6 +60,7 @@
     dict xs = "{" ++ intercalate "," [show k ++ ":" ++ v | (k,v) <- xs] ++ "}"
     list xs = "[" ++ intercalate "," xs ++ "]"
 
+-- | Show a list of 'Idea' values as a JSON string.
 showIdeasJson :: [Idea] -> String
 showIdeasJson ideas = "[" ++ intercalate "\n," (map showIdeaJson ideas) ++ "]"
 
@@ -66,12 +68,13 @@
     show = showEx id
 
 
-showANSI :: IO (Idea -> String)
-showANSI = showEx <$> hsColourConsole
+-- | Show an 'Idea' with ANSI color codes to give syntax coloring to the Haskell code.
+showIdeaANSI :: Idea -> String
+showIdeaANSI = showEx hsColourConsole
 
 showEx :: (String -> String) -> Idea -> String
 showEx tt Idea{..} = unlines $
-    [showSrcLoc (getPointLoc ideaSpan) ++ ": " ++ (if ideaHint == "" then "" else show ideaSeverity ++ ": " ++ ideaHint)] ++
+    [showSrcSpan ideaSpan ++ ": " ++ (if ideaHint == "" then "" else show ideaSeverity ++ ": " ++ ideaHint)] ++
     f "Found" (Just ideaFrom) ++ f "Perhaps" ideaTo ++
     ["Note: " ++ n | let n = showNotes ideaNote, n /= ""]
     where
@@ -84,73 +87,41 @@
 rawIdea :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note]-> [Refactoring R.SrcSpan] -> Idea
 rawIdea = Idea [] []
 
-rawIdea' :: Severity -> String -> GHC.SrcSpan -> String -> Maybe String -> [Note]-> [Refactoring R.SrcSpan] -> Idea
-rawIdea' a b c = Idea [] [] a b (ghcSpanToHSE c)
-
 rawIdeaN :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note] -> Idea
 rawIdeaN a b c d e f = Idea [] [] a b c d e f []
 
-rawIdeaN' :: Severity -> String -> GHC.SrcSpan -> String -> Maybe String -> [Note] -> Idea
-rawIdeaN' a b c d e f = Idea [] [] a b (ghcSpanToHSE c) d e f []
-
-idea :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-        Severity -> String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea
-idea severity hint from to = rawIdea severity hint (srcInfoSpan $ ann from) (f from) (Just $ f to) []
-    where f = trimStart . prettyPrint
+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) []
 
-idea' :: (GHC.HasSrcSpan a, Outputable.Outputable a, GHC.HasSrcSpan b, Outputable.Outputable b) =>
-         Severity -> String -> a -> b -> [Refactoring R.SrcSpan] -> Idea
-idea' severity hint from to =
-  rawIdea severity hint (ghcSpanToHSE (GHC.getLoc from)) (GHC.unsafePrettyPrint from) (Just $ GHC.unsafePrettyPrint to) []
+-- Construct an Idea that suggests "Perhaps you should remove it."
+ideaRemove :: Severity -> String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea
+ideaRemove severity hint span from = rawIdea severity hint span from (Just "") []
 
-suggest :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-           String -> ast SrcSpanInfo -> a -> [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
 
-suggest' :: (GHC.HasSrcSpan a, Outputable.Outputable a, GHC.HasSrcSpan b, Outputable.Outputable b) =>
-            String -> a -> b -> [Refactoring R.SrcSpan] -> Idea
-suggest' = idea' Suggestion
+suggestRemove :: String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea
+suggestRemove = ideaRemove Suggestion
 
-warn :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-        String -> ast SrcSpanInfo -> a -> [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
 
-warn' :: (GHC.HasSrcSpan a, Outputable.Outputable a, GHC.HasSrcSpan b, Outputable.Outputable b) =>
-         String -> a -> b -> [Refactoring R.SrcSpan] -> Idea
-warn' = idea' Warning
-
-ignoreNoSuggestion' :: (GHC.HasSrcSpan a, Outputable.Outputable a)
-                    => String -> a -> Idea
-ignoreNoSuggestion' hint x = rawIdeaN Ignore hint (ghcSpanToHSE (GHC.getLoc x)) (GHC.unsafePrettyPrint x) Nothing []
+ignoreNoSuggestion :: (GHC.Utils.Outputable.Outputable a)
+                    => String -> Located a -> Idea
+ignoreNoSuggestion hint x = rawIdeaN Ignore hint (getLoc x) (unsafePrettyPrint x) Nothing []
 
-ignore :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-          String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea
+ignore :: (GHC.Utils.Outputable.Outputable a) =>
+           String -> Located a -> Located a -> [Refactoring R.SrcSpan] -> Idea
 ignore = idea Ignore
 
-ignore' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>
-           String -> a -> a -> [Refactoring R.SrcSpan] -> Idea
-ignore' = idea' Ignore
-
-ideaN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-         Severity -> String -> ast SrcSpanInfo -> 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 []
 
-ideaN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>
-          Severity -> String -> a -> a -> Idea
-ideaN' severity hint from to = idea' severity hint from to []
-
-suggestN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-            String -> ast SrcSpanInfo -> a -> Idea
+suggestN :: (GHC.Utils.Outputable.Outputable a) =>
+             String -> Located a -> Located a -> Idea
 suggestN = ideaN Suggestion
-
-suggestN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>
-             String -> a -> a -> Idea
-suggestN' = ideaN' Suggestion
-
-ignoreN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) =>
-           String -> ast SrcSpanInfo -> a -> Idea
-ignoreN = ideaN Ignore
-
-ignoreN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) =>
-           String -> a -> a -> Idea
-ignoreN' = ideaN' Ignore
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,43 +1,155 @@
-{-|
-/WARNING: This module represents the old version of the HLint API./
-/It will be deleted in favour of "Language.Haskell.HLint3" in the next major version./
-
-This module provides a library interface to HLint, strongly modelled on the command line interface.
--}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE PatternGuards, RecordWildCards #-}
 
-module Language.Haskell.HLint(hlint, Suggestion, suggestionLocation, suggestionSeverity, Severity(..)) where
+-- |  This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process
+--   and collect the results see 'hlint'.
+--
+--   If you want to approximate the @hlint@ experience with
+--   a more structured API try:
+--
+-- @
+-- (flags, classify, hint) <- 'autoSettings'
+-- Right m <- 'parseModuleEx' flags \"MyFile.hs\" Nothing
+-- print $ 'applyHints' classify hint [m]
+-- @
+module Language.Haskell.HLint(
+    -- * Generate hints
+    hlint, applyHints,
+    -- * Idea data type
+    Idea(..), Severity(..), Note(..), unpackSrcSpan, showIdeaANSI,
+    -- * Settings
+    Classify(..),
+    getHLintDataDir, autoSettings, argsSettings,
+    findSettings, readSettingsFile,
+    -- * Hints
+    Hint,
+    -- * Modules
+    ModuleEx, parseModuleEx, createModuleEx, createModuleExWithFixities, ParseError(..),
+    -- * Parse flags
+    defaultParseFlags,
+    ParseFlags(..), CppFlags(..), FixityInfo,
+    parseFlagsAddFixities,
+    ) where
 
-import qualified HLint
 import Config.Type
+import Config.Read
 import Idea
-import HSE.All
+import Apply qualified as H
+import HLint
+import Fixity
+import GHC.Data.FastString ( unpackFS )
+import GHC.All
+import Hint.All hiding (resolveHints)
+import Hint.All qualified as H
+import GHC.Types.SrcLoc
+import CmdLine
+import Paths_hlint
 
+import Data.List.Extra
+import Data.Maybe
+import System.FilePath
+import Data.Functor
+import Prelude
+import Hint.Restrict qualified as Restrict
 
--- | This function takes a list of command line arguments, and returns the given suggestions.
---   To see a list of arguments type @hlint --help@ at the console.
---   This function writes to the stdout/stderr streams, unless @--quiet@ is specified.
+
+-- | Get the Cabal configured data directory of HLint.
+getHLintDataDir :: IO FilePath
+getHLintDataDir = getDataDir
+
+
+-- | The function produces a tuple containing 'ParseFlags' (for 'parseModuleEx'),
+--   and 'Classify' and 'Hint' for 'applyHints'.
+--   It approximates the normal HLint configuration steps, roughly:
 --
---   As an example:
+-- 1. Use 'findSettings' with 'readSettingsFile' to find and load the HLint settings files.
 --
--- > do hints <- hlint ["src", "--ignore=Use map","--quiet"]
--- >    when (length hints > 3) $ error "Too many hints!"
-hlint :: [String] -> IO [Suggestion]
-hlint = fmap (map Suggestion_) . HLint.hlint
+-- 1. Use 'parseFlagsAddFixities' and 'resolveHints' to transform the outputs of 'findSettings'.
+--
+--   If you want to do anything custom (e.g. using a different data directory, storing intermediate outputs,
+--   loading hints from a database) you are expected to copy and paste this function, then change it to your needs.
+autoSettings :: IO (ParseFlags, [Classify], Hint)
+autoSettings = do
+    (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing
+    pure (parseFlagsAddFixities fixities defaultParseFlags, classify, hints)
 
 
+-- | A version of 'autoSettings' which respects some of the arguments supported by HLint.
+--   If arguments unrecognised by HLint are used it will result in an error.
+--   Arguments which have no representation in the return type are silently ignored.
+argsSettings :: [String] -> IO (ParseFlags, [Classify], Hint)
+argsSettings args = do
+    cmd@CmdMain{..} <- getCmd args
+    -- FIXME: One thing that could be supported (but isn't) is 'cmdGivenHints'
+    (_,settings) <- readAllSettings args cmd
+    let (fixities, classify, hints) = splitSettings settings
+    let flags = parseFlagsSetLanguage (cmdExtensions cmd) $ parseFlagsAddFixities fixities $
+                defaultParseFlags{cppFlags = cmdCpp cmd}
+    let ignore = [Classify Ignore x "" "" | x <- cmdIgnore]
+    pure (flags, classify ++ ignore, hints)
 
--- | A suggestion - the @Show@ instance is of particular use.
-newtype Suggestion = Suggestion_ {fromSuggestion :: Idea}
-                     deriving (Eq,Ord)
 
-instance Show Suggestion where
-    show = show . fromSuggestion
+-- | Given a directory (or 'Nothing' to imply 'getHLintDataDir'), and a module name
+--   (e.g. @HLint.Default@), find the settings file associated with it, returning the
+--   name of the file, and (optionally) the contents.
+--
+--   This function looks for all settings files starting with @HLint.@ in the directory
+--   argument, and all other files relative to the current directory.
+readSettingsFile :: Maybe FilePath -> String -> IO (FilePath, Maybe String)
+readSettingsFile dir x
+    | takeExtension x `elem` [".yml",".yaml"] = do
+        dir <- maybe getHLintDataDir pure dir
+        pure (dir </> x, Nothing)
+    | Just x <- "HLint." `stripPrefix` x = do
+        dir <- maybe getHLintDataDir pure dir
+        pure (dir </> x <.> "hs", Nothing)
+    | otherwise = pure (x <.> "hs", Nothing)
 
--- | From a suggestion, extract the file location it refers to.
-suggestionLocation :: Suggestion -> SrcLoc
-suggestionLocation = getPointLoc . ideaSpan . fromSuggestion
 
+-- | Given a function to load a module (typically 'readSettingsFile'), and a module to start from
+--   (defaults to @hlint.yaml@) find the information from all settings files.
+findSettings :: (String -> IO (FilePath, Maybe String)) -> Maybe String -> IO ([FixityInfo], [Classify], Hint)
+findSettings load start = do
+    (file,contents) <- load $ fromMaybe "hlint.yaml" start
+    splitSettings <$> readFilesConfig [(file,contents)]
 
--- | From a suggestion, determine how severe it is.
-suggestionSeverity :: Suggestion -> Severity
-suggestionSeverity = ideaSeverity . fromSuggestion
+-- | Split a list of 'Setting' for separate use in parsing and hint resolution
+splitSettings :: [Setting] -> ([FixityInfo], [Classify], Hint)
+splitSettings xs =
+    ([x | Infix x <- xs]
+    ,[x | SettingClassify x <- xs]
+    ,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.
+--   The 'Idea' values will be ordered within a file.
+--
+--   Given a set of modules, it may be faster to pass each to 'applyHints' in a singleton list.
+--   When given multiple modules at once this function attempts to find hints between modules,
+--   which is slower and often pointless (by default HLint passes modules singularly, using
+--   @--cross@ to pass all modules together).
+applyHints :: [Classify] -> Hint -> [ModuleEx] -> [Idea]
+applyHints = H.applyHints
+
+-- | Snippet from the documentation, if this changes, update the documentation
+_docs :: IO ()
+_docs = do
+    (flags, classify, hint) <- autoSettings
+    Right m <- parseModuleEx flags "MyFile.hs" Nothing
+    print $ applyHints classify hint [m]
+
+-- | Unpack a 'SrcSpan' value. Useful to allow using the 'Idea' information without
+--   adding a dependency on @ghc@ or @ghc-lib-parser@. Unpacking gives:
+--
+-- > (filename, (startLine, startCol), (endLine, endCol))
+--
+--   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
+    (unpackFS $ srcSpanFile x
+    ,(srcSpanStartLine x, srcSpanStartCol x)
+    ,(srcSpanEndLine x, srcSpanEndCol x))
+unpackSrcSpan _ = Nothing
diff --git a/src/Language/Haskell/HLint3.hs b/src/Language/Haskell/HLint3.hs
deleted file mode 100644
--- a/src/Language/Haskell/HLint3.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-
--- | A reexport of "Language.Haskell.HLint4" for compatibility purposes.
-module Language.Haskell.HLint3(
-    module Language.Haskell.HLint4
-    ) where
-
-import Language.Haskell.HLint4
diff --git a/src/Language/Haskell/HLint4.hs b/src/Language/Haskell/HLint4.hs
deleted file mode 100644
--- a/src/Language/Haskell/HLint4.hs
+++ /dev/null
@@ -1,152 +0,0 @@
-{-# LANGUAGE PatternGuards, RecordWildCards #-}
-
--- | /WARNING: This module represents the evolving version of the HLint API./
---   /It will be renamed to drop the "4" in the next major version./
---
---   This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process
---   and collect the results see 'hlint'. If you want to approximate the @hlint@ experience with
---   a more structured API try:
---
--- @
--- (flags, classify, hint) <- 'autoSettings'
--- Right m <- 'parseModuleEx' flags \"MyFile.hs\" Nothing
--- print $ 'applyHints' classify hint [m]
--- @
-module Language.Haskell.HLint4(
-    hlint, applyHints,
-    -- * Idea data type
-    Idea(..), Severity(..), Note(..),
-    -- * Settings
-    Classify(..),
-    getHLintDataDir, autoSettings, argsSettings,
-    findSettings, readSettingsFile,
-    -- * Hints
-    Hint, resolveHints,
-    -- * Parse files
-    ModuleEx, parseModuleEx, createModuleEx, defaultParseFlags, parseFlagsAddFixities, ParseError(..), ParseFlags(..), CppFlags(..)
-    ) where
-
-import Config.Type
-import Config.Read
-import Idea
-import qualified Apply as H
-import HLint
-import HSE.All
-import Hint.All hiding (resolveHints)
-import qualified Hint.All as H
-import qualified ApiAnnotation as GHC
-import qualified HsSyn as GHC
-import SrcLoc
-import CmdLine
-import Paths_hlint
-import qualified Language.Haskell.GhclibParserEx.Fixity as GhclibParserEx
-
-import Data.List.Extra
-import Data.Maybe
-import System.FilePath
-import Data.Functor
-import Prelude
-
-
--- | Get the Cabal configured data directory of HLint.
-getHLintDataDir :: IO FilePath
-getHLintDataDir = getDataDir
-
-
--- | The function produces a tuple containg 'ParseFlags' (for 'parseModuleEx'),
---   and 'Classify' and 'Hint' for 'applyHints'.
---   It approximates the normal HLint configuration steps, roughly:
---
--- 1. Use 'findSettings' with 'readSettingsFile' to find and load the HLint settings files.
---
--- 1. Use 'parseFlagsAddFixities' and 'resolveHints' to transform the outputs of 'findSettings'.
---
---   If you want to do anything custom (e.g. using a different data directory, storing intermediate outputs,
---   loading hints from a database) you are expected to copy and paste this function, then change it to your needs.
-autoSettings :: IO (ParseFlags, [Classify], Hint)
-autoSettings = do
-    (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing
-    return (parseFlagsAddFixities fixities defaultParseFlags, classify, hints)
-
-
--- | The identity function. In previous versions of HLint this function was useful. Now, it isn't.
-resolveHints :: Hint -> Hint
-resolveHints = id
-
--- | A version of 'autoSettings' which respects some of the arguments supported by HLint.
---   If arguments unrecognised by HLint are used it will result in an error.
---   Arguments which have no representation in the return type are silently ignored.
-argsSettings :: [String] -> IO (ParseFlags, [Classify], Hint)
-argsSettings args = do
-    cmd <- getCmd args
-    case cmd of
-        CmdMain{..} -> do
-            -- FIXME: Two things that could be supported (but aren't) are 'cmdGivenHints' and 'cmdWithHints'.
-            (_,settings) <- readAllSettings args cmd
-            let (fixities, classify, hints) = splitSettings settings
-            let flags = parseFlagsSetLanguage (cmdExtensions cmd) $ parseFlagsAddFixities fixities $
-                        defaultParseFlags{cppFlags = cmdCpp cmd}
-            let ignore = [Classify Ignore x "" "" | x <- cmdIgnore]
-            return (flags, classify ++ ignore, hints)
-        _ -> error "Can only invoke autoSettingsArgs with the root process"
-
-
--- | Given a directory (or 'Nothing' to imply 'getHLintDataDir'), and a module name
---   (e.g. @HLint.Default@), find the settings file associated with it, returning the
---   name of the file, and (optionally) the contents.
---
---   This function looks for all settings files starting with @HLint.@ in the directory
---   argument, and all other files relative to the current directory.
-readSettingsFile :: Maybe FilePath -> String -> IO (FilePath, Maybe String)
-readSettingsFile dir x
-    | takeExtension x `elem` [".yml",".yaml"] = do
-        dir <- maybe getHLintDataDir return dir
-        return (dir </> x, Nothing)
-    | Just x <- "HLint." `stripPrefix` x = do
-        dir <- maybe getHLintDataDir return dir
-        return (dir </> x <.> "hs", Nothing)
-    | otherwise = return (x <.> "hs", Nothing)
-
-
--- | Given a function to load a module (typically 'readSettingsFile'), and a module to start from
---   (defaults to @hlint.yaml@) find the information from all settings files.
-findSettings :: (String -> IO (FilePath, Maybe String)) -> Maybe String -> IO ([Fixity], [Classify], Hint)
-findSettings load start = do
-    (file,contents) <- load $ fromMaybe "hlint.yaml" start
-    splitSettings <$> readFilesConfig [(file,contents)]
-
--- | Split a list of 'Setting' for separate use in parsing and hint resolution
-splitSettings :: [Setting] -> ([Fixity], [Classify], Hint)
-splitSettings xs =
-    ([x | Infix x <- xs]
-    ,[x | SettingClassify x <- xs]
-    ,H.resolveHints $ [Right x | SettingMatchExp x <- xs] ++ map Left [minBound..maxBound])
-
-
--- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's.
---   The 'Idea' values will be ordered within a file.
---
---   Given a set of modules, it may be faster to pass each to 'applyHints' in a singleton list.
---   When given multiple modules at once this function attempts to find hints between modules,
---   which is slower and often pointless (by default HLint passes modules singularly, using
---   @--cross@ to pass all modules together).
-applyHints :: [Classify] -> Hint -> [ModuleEx] -> [Idea]
-applyHints = H.applyHints
-
--- | Snippet from the documentation, if this changes, update the documentation
-_docs :: IO ()
-_docs = do
-    (flags, classify, hint) <- autoSettings
-    Right m <- parseModuleEx flags "MyFile.hs" Nothing
-    print $ applyHints classify hint [m]
-
-
--- | Create a 'ModuleEx' from GHC annotations and module tree.  Note
--- that any hints that work on the @haskell-src-exts@ won't work. It
--- is assumed the incoming parse module has not been adjusted to
--- account for operator fixities.
-createModuleEx:: GHC.ApiAnns -> Located (GHC.HsModule GHC.GhcPs) -> ModuleEx
-createModuleEx anns ast =
-  -- Use builtin fixities.
-  ModuleEx empty [] (GhclibParserEx.applyFixities [] ast) anns
-   where empty = Module an Nothing [] [] []
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -1,7 +1,7 @@
 
 module Main(main) where
 
-import Language.Haskell.HLint3
+import Language.Haskell.HLint
 import Control.Monad
 import System.Environment
 import System.Exit
diff --git a/src/Parallel.hs b/src/Parallel.hs
--- a/src/Parallel.hs
+++ b/src/Parallel.hs
@@ -3,7 +3,7 @@
 
 import Control.Parallel.Strategies
 parallel :: [IO [a]] -> IO [[a]]
-parallel = return . withStrategy (parList $ seqList r0) . map unsafePerformIO
+parallel = pure . withStrategy (parList $ seqList r0) . map unsafePerformIO
 
 However, this version performs about 10% slower with 2 processors in GHC 6.12.1
 -}
@@ -21,11 +21,11 @@
 
 
 parallel1 :: [IO a] -> IO [a]
-parallel1 [] = return []
+parallel1 [] = pure []
 parallel1 (x:xs) = do
     x2 <- x
     xs2 <- unsafeInterleaveIO $ parallel1 xs
-    return $ x2:xs2
+    pure $ x2:xs2
 
 
 parallelN :: Int -> [IO a] -> IO [a]
@@ -40,7 +40,7 @@
         f chan = do
             v <- readChan chan
             case v of
-                Nothing -> return ()
+                Nothing -> pure ()
                 Just (m,x) -> do
                     putMVar m =<< try x
                     f chan
diff --git a/src/Refact.hs b/src/Refact.hs
--- a/src/Refact.hs
+++ b/src/Refact.hs
@@ -1,34 +1,88 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE LambdaCase #-}
+
 module Refact
-    ( toRefactSrcSpan
-    , toSS, toSS'
-    , toSrcSpan'
+    ( substVars
+    , toRefactSrcSpan
+    , toSS, toSSA, toSSAnc
+    , checkRefactor, refactorPath, runRefactoring
     ) where
 
-import qualified Refact.Types as R
-import HSE.All
+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
+import System.Console.CmdArgs.Verbosity
+import System.Directory.Extra
+import System.Exit
+import System.IO.Extra
+import System.Process.Extra
+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
 
-toRefactSrcSpan :: SrcSpan -> R.SrcSpan
-toRefactSrcSpan ss = R.SrcSpan (srcSpanStartLine ss)
-                               (srcSpanStartColumn ss)
-                               (srcSpanEndLine ss)
-                               (srcSpanEndColumn ss)
+import GHC.Util.SrcLoc (getAncLoc)
 
-toSS :: Annotated a => a S -> R.SrcSpan
-toSS = toRefactSrcSpan . srcInfoSpan . ann
+substVars :: [String]
+substVars = [letter : number | number <- "" : map show [0..], letter <- ['a'..'z']]
 
--- | Don't crash in case ghc gives us a \"fake\" span,
--- opting instead to show @0 0 0 0@ coordinates.
-toSrcSpan' :: GHC.HasSrcSpan a => a -> R.SrcSpan
-toSrcSpan' x = case GHC.getLoc x of
-    GHC.RealSrcSpan span ->
+toRefactSrcSpan :: GHC.SrcSpan -> R.SrcSpan
+toRefactSrcSpan = \case
+    GHC.RealSrcSpan span _ ->
         R.SrcSpan (GHC.srcSpanStartLine span)
                   (GHC.srcSpanStartCol span)
                   (GHC.srcSpanEndLine span)
                   (GHC.srcSpanEndCol span)
     GHC.UnhelpfulSpan _ ->
-        R.SrcSpan 0 0 0 0
+        R.SrcSpan (-1) (-1) (-1) (-1)
 
-toSS' :: GHC.HasSrcSpan e => e -> R.SrcSpan
-toSS' = toRefactSrcSpan . ghcSpanToHSE . GHC.getLoc
+-- | Don't crash in case ghc gives us a \"fake\" span,
+-- opting instead to show @-1 -1 -1 -1@ coordinates.
+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
+
+refactorPath :: Maybe FilePath -> IO (Either String FilePath)
+refactorPath rpath = do
+    let excPath = fromMaybe "refactor" rpath
+    mexc <- findExecutable excPath
+    case mexc of
+        Just exc -> do
+            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 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"
+                       , "'refactor' is provided by the 'apply-refact' package and has to be installed"
+                       , "<https://github.com/mpickering/apply-refact>"
+                       ]
+
+runRefactoring :: FilePath -> FilePath -> FilePath -> [Extension] -> [Extension] -> String -> IO ExitCode
+runRefactoring rpath fin hints enabled disabled opts =  do
+    let args = [fin, "-v0"] ++ words opts ++ ["--refact-file", hints]
+          ++ [arg | e <- enabled, arg <- ["-X", show e]]
+          ++ [arg | e <- disabled, arg <- ["-X", "No" ++ show e]]
+    whenLoud $ putStrLn $ "Running refactor: " ++ showCommandForUser rpath args
+    (_, _, _, phand) <- createProcess $ proc rpath args
+    try $ hSetBuffering stdin LineBuffering :: IO (Either IOException ())
+    hSetBuffering stdout LineBuffering
+    -- Propagate the exit code from the spawn process
+    waitForProcess phand
+
+minRefactorVersion :: Version
+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 HSE.All
 import Timing
 import Paths_hlint
 import HsColour
 import EmbedData
+import GHC.Util qualified as GHC
 
 
 writeTemplate :: FilePath -> [(String,[String])] -> FilePath -> IO ()
@@ -28,7 +29,7 @@
     where
         generateIds :: [String] -> [(String,Int)] -- sorted by name
         generateIds = map (NE.head &&& length) . NE.group -- must be already sorted
-        files = generateIds $ sort $ map (srcSpanFilename . ideaSpan) ideas
+        files = generateIds $ sort $ map (GHC.srcSpanFilename . ideaSpan) ideas
         hints = generateIds $ map hintName $ sortOn (negate . fromEnum . ideaSeverity &&& hintName) ideas
         hintName x = show (ideaSeverity x) ++ ": " ++ ideaHint x
 
@@ -41,10 +42,10 @@
                          ("HINTS",list "hint" hints),("FILES",list "file" files)]
 
         content = concatMap (\i -> writeIdea (getClass i) i) ideas
-        getClass i = "hint" ++ f hints (hintName i) ++ " file" ++ f files (srcSpanFilename $ ideaSpan i)
+        getClass i = "hint" ++ f hints (hintName i) ++ " file" ++ f files (GHC.srcSpanFilename $ ideaSpan i)
             where f xs x = show $ fromJust $ findIndex ((==) x . fst) xs
 
-        list mode = zipWith f [0..]
+        list mode = zipWithFrom f 0
             where
                 f i (name,n) = "<li><a id=" ++ show id ++ " href=\"javascript:show('" ++ id ++ "')\">" ++
                                escapeHTML name ++ " (" ++ show n ++ ")</a></li>"
@@ -54,7 +55,7 @@
 writeIdea :: String -> Idea -> [String]
 writeIdea cls Idea{..} =
     ["<div class=" ++ show cls ++ ">"
-    ,escapeHTML (showSrcLoc (getPointLoc ideaSpan) ++ ": " ++ show ideaSeverity ++ ": " ++ ideaHint) ++ "<br/>"
+    ,escapeHTML (GHC.showSrcSpan ideaSpan ++ ": " ++ show ideaSeverity ++ ": " ++ ideaHint) ++ "<br/>"
     ,"Found<br/>"
     ,hsColourHTML ideaFrom] ++
     (case ideaTo of
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
new file mode 100644
--- /dev/null
+++ b/src/Summary.hs
@@ -0,0 +1,202 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE DataKinds #-}
+
+module Summary (generateMdSummary, generateJsonSummary, generateExhaustiveConfig) where
+
+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
+
+import Idea
+import Apply
+import Hint.Type
+import Hint.All
+import Config.Type
+import Test.Annotations
+import Deriving.Aeson
+import Data.Aeson (encode)
+import Data.ByteString.Char8 (unpack)
+import Data.ByteString.Lazy (toStrict)
+
+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
+
+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
+
+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@.
+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 dedupBuiltin <$> concatForM tests $ \(TestCase _ _ inp _ _) -> do
+            m <- parseModuleEx defaultParseFlags file (Just inp)
+            pure $ case m of
+                Right m -> map (ideaToValue category inp) $ applyHints [] hint [m]
+                Left _ -> []
+    where
+        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) 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
+
+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) <- groupHintsByCategory sBuiltinRules] ++
+  [ ""
+  , "## Configured hints"
+  , ""
+  ]
+  ++ lhsRhsTable sLhsRhsRules
+  where
+    groupHintsByCategory = Map.toAscList . Map.fromListWith (<>) . fmap keyCategory
+    keyCategory hint = (hCategory hint, [hint])
+
+row :: [String] -> [String]
+row xs = ["<tr>"] ++ xs ++ ["</tr>"]
+
+-- | Render using <code> if it is single-line, otherwise using <pre>.
+haskell :: String -> [String]
+haskell s
+  | '\n' `elem` s = ["<pre>", s, "</pre>"]
+  | otherwise = ["<code>", s, "</code>", "<br>"]
+
+builtinTable :: [BuiltinHint] -> [String]
+builtinTable builtins =
+  ["<table>"]
+  ++ row ["<th>Hint Name</th>", "<th>Hint</th>", "<th>Severity</th>"]
+  ++ concatMap showBuiltin builtins
+  ++ ["</table>"]
+
+showBuiltin :: BuiltinHint -> [String]
+showBuiltin BuiltinHint{..} = row1
+  where
+    row1 = row $
+      [ "<td>" ++ hName ++ "</td>", "<td>"]
+      ++ showExample (NE.head (NE.fromList hExamples))
+      ++ ["Does not support refactoring." | not hRefactoring]
+      ++ ["</td>"] ++
+      [ "<td>" ++ show hSeverity ++ "</td>"
+      ]
+    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 =
+  ["<table>"]
+  ++ row ["<th>Hint Name</th>", "<th>Hint</th>", "<th>Severity</th>"]
+  ++ concatMap showLhsRhs hints
+  ++ ["</table>"]
+
+showLhsRhs :: HintRule -> [String]
+showLhsRhs HintRule{..} = row $
+  [ "<td>" ++ hintRuleName ++ "</td>"
+  , "<td>"
+  , "LHS:"
+  ]
+  ++ haskell (show hintRuleLHS)
+  ++ ["RHS:"]
+  ++ haskell (show hintRuleRHS)
+  ++
+  [ "</td>"
+  , "<td>" ++ show hintRuleSeverity ++ "</td>"
+  ]
diff --git a/src/Test/All.hs b/src/Test/All.hs
--- a/src/Test/All.hs
+++ b/src/Test/All.hs
@@ -1,14 +1,16 @@
 {-# LANGUAGE RecordWildCards #-}
-{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
 
 module Test.All(test) where
 
 import Control.Exception
-import System.Console.CmdArgs
 import Control.Monad
 import Control.Monad.IO.Class
 import Data.Char
+import Data.Either.Extra
+import Data.Foldable
 import Data.List
+import Data.Maybe
 import System.Directory
 import System.FilePath
 import Data.Functor
@@ -17,59 +19,59 @@
 import Config.Type
 import Config.Read
 import CmdLine
-import HSE.All
+import Refact
 import Hint.All
-import Test.Util
-import Test.InputOutput
 import Test.Annotations
-import Test.Translate
+import Test.InputOutput
+import Test.Util
 import System.IO.Extra
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
 
 test :: Cmd -> ([String] -> IO ()) -> FilePath -> [FilePath] -> IO Int
-test CmdTest{..} main dataDir files = do
+test CmdMain{..} main dataDir files = do
+    rpath <- refactorPath (if cmdWithRefactor == "" then Nothing else Just cmdWithRefactor)
+
     (failures, ideas) <- withBuffering stdout NoBuffering $ withTests $ do
         hasSrc <- liftIO $ doesFileExist "hlint.cabal"
-        useSrc <- return $ hasSrc && null files
-        testFiles <- if files /= [] then return files else do
+        let useSrc = hasSrc && null files
+        testFiles <- if files /= [] then pure files else do
             xs <- liftIO $ getDirectoryContents dataDir
-            return [dataDir </> x | x <- xs, takeExtension x `elem` [".hs",".yml",".yaml"]
-                                , not $ "HLint_" `isPrefixOf` takeBaseName x]
+            pure [dataDir </> x | x <- xs, takeExtension x `elem` [".yml",".yaml"]]
         testFiles <- liftIO $ forM testFiles $ \file -> do
-            hints <- readFilesConfig [(file, Nothing)]
-            return (file, hints ++ (if takeBaseName file /= "Test" then [] else map (Builtin . fst) builtinHints))
+            hints <- readFilesConfig [(file, Nothing),("CommandLine.yaml", Just "- group: {name: testing, enabled: true}")]
+            pure (file, hints ++ (if takeBaseName file /= "Test" then [] else map (Builtin . fst) builtinHints))
         let wrap msg act = do liftIO $ putStr (msg ++ " "); act; liftIO $ putStrLn ""
 
-        liftIO $ putStrLn "Testing"
+        liftIO $ putStrLn $ "Testing (" ++ (if isRight rpath then "with" else "WITHOUT") ++ " refactoring)"
         liftIO $ checkCommentedYaml $ dataDir </> "default.yaml"
         when useSrc $ wrap "Source annotations" $ do
             config <- liftIO $ readFilesConfig [(".hlint.yaml",Nothing)]
             forM_ builtinHints $ \(name,_) -> do
                 progress
-                testAnnotations (Builtin name : if name == "Restrict" then config else []) $ "src/Hint" </> name <.> "hs"
+                testAnnotations (Builtin name : if name == "Restrict" then config else [])
+                                ("src/Hint" </> name <.> "hs")
+                                (eitherToMaybe rpath)
         when useSrc $ wrap "Input/outputs" $ testInputOutput main
 
         wrap "Hint names" $ mapM_ (\x -> do progress; testNames $ snd x) testFiles
-        wrap "Hint annotations" $ forM_ testFiles $ \(file,h) -> do progress; testAnnotations h file
-        let hs = [h | (file, h) <- testFiles, takeFileName file /= "Test.hs"]
-        when cmdTypeCheck $ wrap "Hint typechecking" $
-            progress >> testTypeCheck cmdDataDir cmdTempDir hs
-        when cmdQuickCheck $ wrap "Hint QuickChecking" $
-            progress >> testQuickCheck cmdDataDir cmdTempDir hs
+        wrap "Hint annotations" $ forM_ testFiles $ \(file,h) -> do progress; testAnnotations h file (eitherToMaybe rpath)
 
         when (null files && not hasSrc) $ liftIO $ putStrLn "Warning, couldn't find source code, so non-hint tests skipped"
-        getIdeas
-    whenLoud $ mapM_ print ideas
-    return failures
 
+    case rpath of
+        Left refactorNotFound -> putStrLn $ unlines [refactorNotFound, "Refactoring tests skipped"]
+        _ -> pure ()
+    pure failures
 
+
 ---------------------------------------------------------------------
 -- VARIOUS SMALL TESTS
 
 -- Check all hints in the standard config files get sensible names
 testNames :: [Setting] -> Test ()
 testNames hints = sequence_
-    [ failed ["No name for the hint " ++ prettyPrint hintRuleLHS ++ " ==> " ++ prettyPrint hintRuleRHS]
+    [ failed ["No name for the hint " ++ unsafePrettyPrint hintRuleLHS ++ " ==> " ++ unsafePrettyPrint hintRuleRHS]
     | SettingMatchExp x@HintRule{..} <- hints, hintRuleName == defaultHintName]
 
 
diff --git a/src/Test/Annotations.hs b/src/Test/Annotations.hs
--- a/src/Test/Annotations.hs
+++ b/src/Test/Annotations.hs
@@ -1,51 +1,71 @@
-{-# LANGUAGE PatternGuards, RecordWildCards, ViewPatterns #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE CPP, PatternGuards, RecordWildCards, ViewPatterns #-}
 
 -- | Check the <TEST> annotations within source and hint files.
-module Test.Annotations(testAnnotations) where
+module Test.Annotations(testAnnotations, parseTestFile, TestCase(..)) where
 
 import Control.Exception.Extra
-import Data.Tuple.Extra
+import Control.Monad
+import Control.Monad.IO.Class
 import Data.Char
 import Data.Either.Extra
+import Data.Function
+import Data.Functor
 import Data.List.Extra
 import Data.Maybe
-import Control.Monad
+import Data.Tuple.Extra
+import System.Exit
 import System.FilePath
-import Control.Monad.IO.Class
-import Data.Function
-import Data.Yaml
-import qualified Data.ByteString.Char8 as BS
+import System.IO.Extra
+import GHC.All
+import Data.ByteString.Char8 qualified as BS
 
 import Config.Type
 import Idea
 import Apply
-import HSE.All
+import Extension
+import Refact
 import Test.Util
-import Data.Functor
 import Prelude
 import Config.Yaml
+import GHC.Data.FastString
 
+import GHC.Util
+import GHC.Types.SrcLoc
+import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
 
+#ifdef HS_YAML
+
+import Data.YAML.Aeson (decode1Strict)
+import Data.YAML (Pos)
+import Data.ByteString (ByteString)
+
+decodeEither' :: ByteString -> Either (Pos, String) ConfigYaml
+decodeEither' = decode1Strict
+
+#else
+
+import Data.Yaml
+
+#endif
+
 -- Input, Output
 -- Output = Nothing, should not match
 -- Output = Just xs, should match xs
-data TestCase = TestCase SrcLoc String (Maybe String) [Setting] deriving (Show)
+data TestCase = TestCase SrcLoc Refactor String (Maybe String) [Setting] deriving (Show)
 
-testAnnotations :: [Setting] -> FilePath -> Test ()
-testAnnotations setting file = do
+data Refactor = TestRefactor | SkipRefactor deriving (Eq, Show)
+
+testAnnotations :: [Setting] -> FilePath -> Maybe FilePath -> Test ()
+testAnnotations setting file rpath = do
     tests <- liftIO $ parseTestFile file
     mapM_ f tests
     where
-        f (TestCase loc inp out additionalSettings) = do
+        f (TestCase loc refact inp out additionalSettings) = do
             ideas <- liftIO $ try_ $ do
                 res <- applyHintFile defaultParseFlags (setting ++ additionalSettings) file $ Just inp
                 evaluate $ length $ show res
-                return res
-
-            -- the hints from data/Test.hs are really fake hints we don't actually deploy
-            -- so don't record them
-            when (takeFileName file /= "Test.hs") $
-                either (const $ return ()) addIdeas ideas
+                pure res
 
             let good = case (out, ideas) of
                     (Nothing, Right []) -> True
@@ -54,19 +74,36 @@
             let bad =
                     [failed $
                         ["TEST FAILURE (" ++ show (either (const 1) length ideas) ++ " hints generated)"
-                        ,"SRC: " ++ showSrcLoc loc
+                        ,"SRC: " ++ unsafePrettyPrint loc
                         ,"INPUT: " ++ inp] ++
-                        map ("OUTPUT: " ++) (either (return . show) (map show) ideas) ++
+                        map ("OUTPUT: " ++) (either (pure . show) (map show) ideas) ++
                         ["WANTED: " ++ fromMaybe "<failure>" out]
                         | not good] ++
                     [failed
                         ["TEST FAILURE (BAD LOCATION)"
-                        ,"SRC: " ++ showSrcLoc loc
+                        ,"SRC: " ++ unsafePrettyPrint loc
                         ,"INPUT: " ++ inp
                         ,"OUTPUT: " ++ show i]
-                        | i@Idea{..} <- fromRight [] ideas, let SrcLoc{..} = getPointLoc ideaSpan, srcFilename == "" || srcLine == 0 || srcColumn == 0]
-            if null bad then passed else sequence_ bad
+                        | i@Idea{..} <- fromRight [] ideas, let SrcLoc{..} = srcSpanStart ideaSpan, srcFilename == "" || srcLine == 0 || srcColumn == 0]
+                        -- TODO: shouldn't these checks be == -1 instead?
 
+            -- Skip refactoring test if the hlint test failed, or if the
+            -- test is annotated with @NoRefactor.
+            let skipRefactor = notNull bad || refact == SkipRefactor
+            badRefactor <- if skipRefactor then pure [] else liftIO $ do
+                refactorErr <- case ideas of
+                    Right [] -> testRefactor rpath Nothing inp
+                    Right [idea] -> testRefactor rpath (Just idea) inp
+                    -- Skip refactoring test if there are multiple hints
+                    _ -> pure []
+                pure $ [failed $
+                           ["TEST FAILURE (BAD REFACTORING)"
+                           ,"SRC: " ++ unsafePrettyPrint loc
+                           ,"INPUT: " ++ inp] ++ refactorErr
+                           | notNull refactorErr]
+
+            if null bad && null badRefactor then passed else sequence_ (bad ++ badRefactor)
+
         match "???" _ = True
         match (word1 -> ("@Message",msg)) i = ideaHint i == msg
         match (word1 -> ("@Note",note)) i = map show (ideaNote i) == [note]
@@ -81,13 +118,16 @@
 parseTestFile :: FilePath -> IO [TestCase]
 parseTestFile file =
     -- we remove all leading # symbols since Yaml only lets us do comments that way
-    f Nothing . zip [1..] . map (\x -> fromMaybe x $ stripPrefix "# " x) . lines <$> readFile file
+    f Nothing TestRefactor . zipFrom 1 . map (dropPrefix "# ") . lines <$> readFile file
     where
         open :: String -> Maybe [Setting]
         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]
@@ -96,20 +136,58 @@
         shut :: String -> Bool
         shut = isPrefixOf "</TEST>"
 
-        f :: Maybe [Setting] -> [(Int, String)] -> [TestCase]
-        f Nothing ((i,x):xs) = f (open x) xs
-        f (Just s)  ((i,x):xs)
-            | shut x = f Nothing xs
-            | null x || "-- " `isPrefixOf` x = f (Just s) xs
-            | "\\" `isSuffixOf` x, (_,y):ys <- xs = f (Just s) $ (i,init x++"\n"++y):ys
-            | otherwise = parseTest file i x s : f (Just s) xs
-        f _ [] = []
+        f :: Maybe [Setting] -> Refactor -> [(Int, String)] -> [TestCase]
+        f Nothing _ ((i,x):xs) = f (open x) TestRefactor xs
+        f (Just s) refact ((i,x):xs)
+            | shut x = f Nothing TestRefactor xs
+            | Just (x',_) <- stripInfix "@NoRefactor" x =
+                f (Just s) SkipRefactor ((i, trimEnd x' ++ ['\\' | "\\" `isSuffixOf` x]) : xs)
+            | null x || "-- " `isPrefixOf` x = f (Just s) refact xs
+            | Just x <- stripSuffix "\\" x, (_,y):ys <- xs = f (Just s) refact $ (i,x++"\n"++y):ys
+            | otherwise = parseTest refact file i x s : f (Just s) TestRefactor xs
+        f _ _ [] = []
 
 
-parseTest :: String -> Int -> String -> [Setting] -> TestCase
-parseTest file i x = uncurry (TestCase (SrcLoc file i 0)) $ f x
+parseTest :: Refactor -> String -> Int -> String -> [Setting] -> TestCase
+parseTest refact file i x = uncurry (TestCase (mkSrcLoc (mkFastString file) i 0) refact) $ f x
     where
         f x | Just x <- stripPrefix "<COMMENT>" x = first ("--"++) $ f x
-        f (' ':'-':'-':xs) | null xs || " " `isPrefixOf` xs = ("", Just $ dropWhile isSpace xs)
+        f (' ':'-':'-':xs) | null xs || " " `isPrefixOf` xs = ("", Just $ trimStart xs)
         f (x:xs) = first (x:) $ f xs
         f [] = ([], Nothing)
+
+
+-- Returns an empty list if the refactoring test passes, otherwise
+-- returns error messages.
+testRefactor :: Maybe FilePath -> Maybe Idea -> String -> IO [String]
+-- Skip refactoring test if the refactor binary is not found.
+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 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 []
+testRefactor (Just rpath) (Just idea) inp = withTempFile $ \tempInp -> withTempFile $ \tempHints -> do
+    let refact = (show idea, ideaRefactoring idea)
+        -- Ignores spaces and semicolons since unsafePrettyPrint may differ from apply-refact.
+        process = filter (\c -> not (isSpace c) && c /= ';')
+        matched expected g actual = process expected `g` process actual
+        x `isProperSubsequenceOf` y = x /= y && x `isSubsequenceOf` y
+    writeFile tempInp inp
+    writeFile tempHints (show [refact])
+    exitCode <- runRefactoring rpath tempInp tempHints defaultExtensions [] "--inplace"
+    refactored <- readFile tempInp
+    pure $ case exitCode of
+        ExitFailure ec -> ["Refactoring failed: exit code " ++ show ec]
+        ExitSuccess -> case ideaTo idea of
+            -- The hint's suggested replacement is @Just ""@, which means the hint
+            -- suggests removing something from the input. The refactoring output
+            -- should be a proper subsequence of the input.
+            Just "" | not (matched refactored isProperSubsequenceOf inp) ->
+                ["Refactor output is expected to be a proper subsequence of: " ++ inp, "Actual: " ++ refactored]
+            -- The hint has a suggested replacement. The suggested replacement
+            -- should be a substring of the refactoring output.
+            Just to | not (matched to isInfixOf refactored) ->
+                ["Refactor output is expected to contain: " ++ to, "Actual: " ++ refactored]
+            _ -> []
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
 
@@ -24,10 +26,10 @@
 testInputOutput :: ([String] -> IO ()) -> Test ()
 testInputOutput main = do
     xs <- liftIO $ getDirectoryContents "tests"
-    xs <- return $ filter ((==) ".test" . takeExtension) xs
+    xs <- pure $ filter ((==) ".test" . takeExtension) xs
     forM_ xs $ \file -> do
         ios <- liftIO $ parseInputOutputs <$> readFile ("tests" </> file)
-        forM_ (zip [1..] ios) $ \(i,io@InputOutput{..}) -> do
+        forM_ (zipFrom 1 ios) $ \(i,io@InputOutput{..}) -> do
             progress
             liftIO $ forM_ files $ \(name,contents) -> do
                 createDirectoryIfMissing True $ takeDirectory name
@@ -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
 
 
 ---------------------------------------------------------------------
@@ -71,7 +75,7 @@
         handle (\(e::ExitCode) -> writeIORef code e) $
         bracket getVerbosity setVerbosity $ const $ setVerbosity Normal >> main run
     code <- liftIO $ readIORef code
-    (want,got) <- return $ matchStarStar (lines output) got
+    (want,got) <- pure $ matchStarStar (lines output) got
 
     if maybe False (/= code) exit then
         failed
@@ -96,7 +100,12 @@
 -- | First string may have stars in it (the want)
 matchStar :: String -> String -> Bool
 matchStar ('*':xs) ys = any (matchStar xs) $ tails ys
-matchStar (x:xs) (y:ys) = x == y && matchStar xs ys
+matchStar ('/':x:xs) ('\\':'\\':ys) | x /= '/' = matchStar (x:xs) ys -- JSON escaped newlines
+matchStar (x:xs) (y:ys) = eq x y && matchStar xs ys
+    where
+        -- allow path differences between Windows and Linux
+        eq '/' y = isPathSeparator y
+        eq x y = x == y
 matchStar [] [] = True
 matchStar _ _ = False
 
diff --git a/src/Test/Proof.hs b/src/Test/Proof.hs
deleted file mode 100644
--- a/src/Test/Proof.hs
+++ /dev/null
@@ -1,186 +0,0 @@
-{-# LANGUAGE RecordWildCards, PatternGuards, FlexibleContexts #-}
-
--- | Check the coverage of the hints given a list of Isabelle theorems
-module Test.Proof(proof) where
-
-import Data.Tuple.Extra
-import Control.Applicative
-import Control.Monad
-import Control.Monad.Trans.State
-import Data.Char
-import Data.List.Extra
-import Data.Maybe
-import Data.Function
-import System.FilePath
-import Config.Type
-import HSE.All
-import Prelude
-
-
-data Theorem = Theorem
-    {original :: Maybe HintRule
-    ,location :: String
-    ,lemma :: String
-    }
-
-instance Eq Theorem where
-    t1 == t2 = lemma t1 == lemma t2
-
-instance Ord Theorem where
-    compare t1 t2 = compare (lemma t1) (lemma t2)
-
-instance Show Theorem where
-    show Theorem{..} = location ++ ":\n" ++ maybe "" f original ++ lemma ++ "\n"
-        where f HintRule{..} = "(* " ++ prettyPrint hintRuleLHS ++ " ==> " ++ prettyPrint hintRuleRHS ++ " *)\n"
-
-proof :: [FilePath] -> [Setting] -> FilePath -> IO ()
-proof reports hints thy = do
-    got <- isabelleTheorems (takeFileName thy) <$> readFile thy
-    let want = nubOrd $ hintTheorems hints
-    let unused = got \\ want
-    let missing = want \\ got
-    let reasons = map (\x -> (fst $ head x, map snd x)) $ groupBy ((==) `on` fst) $
-                  sortBy (compare `on` fst) $ map (classifyMissing &&& id) missing
-    let summary = table $ let (*) = (,) in
-            ["HLint hints" * want
-            ,"HOL proofs" * got
-            ,"Useful proofs" * (got `intersect` want)
-            ,"Unused proofs" * unused
-            ,"Unproved hints" * missing] ++
-            [("  " ++ name) * ps | (name,ps) <- reasons]
-    putStr $ unlines summary
-    forM_ reports $ \report -> do
-        let out = ("Unused proofs",unused) : map (first ("Unproved hints - " ++)) reasons
-        writeFile report $ unlines $ summary ++ "" : concat
-            [("== " ++ a ++ " ==") : "" : map show b | (a,b) <- out]
-        putStrLn $ "Report written to " ++ report
-    where
-        table xs = [a ++ replicate (n + 6 - length a - length bb) ' ' ++ bb | (a,b) <- xs, let bb = show $ length b]
-            where n = maximum $ map (length . fst) xs
-
-
-missingFuncs :: [(String, String)]
-missingFuncs = let a*b = [(b,a) | b <- words b] in concat
-    ["IO" * "putChar putStr print putStrLn getLine getChar getContents hReady hPrint stdin"
-    ,"Exit" * "exitSuccess"
-    ,"Ord" * "(>) (<=) (>=) (<) compare minimum maximum sort sortBy"
-    ,"Show" * "show shows showIntAtBase"
-    ,"Read" * "reads read"
-    ,"String" * "lines unlines words unwords"
-    ,"Monad" * "mapM mapM_ sequence sequence_ msum mplus mzero liftM when unless return evaluate join void (>>=) (<=<) (>=>) forever ap"
-    ,"Functor" * "fmap"
-    ,"Numeric" * "(+) (*) fromInteger fromIntegral negate log (/) (-) (*) (^^) (^) subtract sqrt even odd"
-    ,"Char" * "isControl isPrint isUpper isLower isAlpha isDigit"
-    ,"Arrow" * "second first (***) (&&&)"
-    ,"Applicative+" * "traverse for traverse_ for_ pure (<|>) (<**>)"
-    ,"Exception" * "catch handle catchJust bracket error toException"
-    ,"WeakPtr" * "mkWeak"
-    ]
-
-
--- | Guess why a theorem is missing
-classifyMissing :: Theorem -> String
-classifyMissing Theorem{original = Just HintRule{..}}
-    | _:_ <- [v :: Exp_ | v@Case{} <- universeBi (hintRuleLHS,hintRuleRHS)] = "case"
-    | _:_ <- [v :: Exp_ | v@ListComp{} <- universeBi (hintRuleLHS,hintRuleRHS)] = "list-comp"
-    | v:_ <- mapMaybe (`lookup` missingFuncs) [prettyPrint (v :: Name S) | v <- universeBi (hintRuleLHS,hintRuleRHS)] = v
-classifyMissing _ = "?unknown"
-
-
--- Extract theorems out of Isabelle code (HLint.thy)
-isabelleTheorems :: FilePath -> String -> [Theorem]
-isabelleTheorems file = find . lexer 1
-    where
-        find ((i,"lemma"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) lemma : find rest
-        find ((i,"lemma"):(_,name):(_,":"):(_,'\"':lemma):rest) = Theorem Nothing (file ++ ":" ++ show i) lemma : find rest
-        find ((i,"lemma"):(_,"assumes"):(_,'\"':assumes):(_,"shows"):(_,'\"':lemma):rest) =
-            Theorem Nothing (file ++ ":" ++ show i) (assumes ++ " \\<Longrightarrow> " ++ lemma) : find rest
-
-        find ((i,"lemma"):rest) = Theorem Nothing (file ++ ":" ++ show i) "Unsupported lemma format" : find rest
-        find (x:xs) = find xs
-        find [] = []
-
-        lexer i x
-            | i `seq` False = []
-            | Just x <- stripPrefix "(*" x, (a,b) <- breaks "*)" x = lexer (add a i) b
-            | Just x <- stripPrefix "\"" x, (a,b) <- breaks "\"" x = (i,'\"':a) : lexer (add a i) b -- NOTE: drop the final "
-            | x:xs <- x, isSpace x = lexer (add [x] i) xs
-            | (a@(_:_),b) <- span (\y -> y == '_' || isAlpha y) x = (i,a) : lexer (add a i) b
-        lexer i (x:xs) = (i,[x]) : lexer (add [x] i) xs
-        lexer i [] = []
-
-        add s i = length (filter (== '\n') s) + i
-
-        breaks s x | Just x <- stripPrefix s x = ("",x)
-        breaks s (x:xs) = let (a,b) = breaks s xs in (x:a,b)
-        breaks s [] = ([],[])
-
-
-reparen :: Setting -> Setting
-reparen (SettingMatchExp m@HintRule{..}) = SettingMatchExp m{hintRuleLHS = f False hintRuleLHS, hintRuleRHS = f True hintRuleRHS}
-    where f right x = if isLambda x || isIf x || badInfix x then Paren (ann x) x else x
-          badInfix (InfixApp _ _ op _) = prettyPrint op `elem` words "|| && ."
-          badInfix _ = False
-reparen x = x
-
-
--- Extract theorems out of the hints
-hintTheorems :: [Setting] -> [Theorem]
-hintTheorems xs =
-    [ Theorem (Just m) (loc $ ann hintRuleLHS) $ maybe "" assumes hintRuleSide ++ relationship hintRuleNotes a b
-    | SettingMatchExp m@HintRule{..} <- map reparen xs, let a = exp1 $ typeclasses hintRuleNotes hintRuleLHS, let b = exp1 hintRuleRHS, a /= b]
-    where
-        loc (SrcSpanInfo (SrcSpan file ln _ _ _) _) = takeFileName file ++ ":" ++ show ln
-
-        subs xs = flip lookup [(reverse b, reverse a) | x <- words xs, let (a,'=':b) = break (== '=') $ reverse x]
-        funs = subs "id=ID not=neg or=the_or and=the_and (||)=tror (&&)=trand (++)=append (==)=eq (/=)=neq ($)=dollar"
-        ops = subs "||=orelse &&=andalso .=oo ===eq /==neq ++=++ !!=!! $=dollar $!=dollarBang"
-        pre = flip elem $ words "eq neq dollar dollarBang"
-        cons = subs "True=TT False=FF"
-
-        typeclasses hintRuleNotes x = foldr f x hintRuleNotes
-            where
-                f (ValidInstance cls var) x = evalState (transformM g x) True
-                    where g v@Var{} | v ~= var = do
-                                b <- get; put False
-                                return $ if b then Paren an $ toNamed $ prettyPrint v ++ "::'a::" ++ cls ++ "_sym" else v
-                          g v = return v :: State Bool Exp_
-                f _  x = x
-
-        relationship hintRuleNotes a b | any lazier hintRuleNotes = a ++ " \\<sqsubseteq> " ++ b
-                               | DecreasesLaziness `elem` hintRuleNotes = b ++ " \\<sqsubseteq> " ++ a
-                               | otherwise = a ++ " = " ++ b
-            where lazier IncreasesLaziness = True
-                  lazier RemovesError{} = True
-                  lazier _ = False
-
-        assumes (App _ op var)
-            | op ~= "isNat" = "le\\<cdot>0\\<cdot>" ++ prettyPrint var ++ " \\<noteq> FF \\<Longrightarrow> "
-            | op ~= "isNegZero" = "gt\\<cdot>0\\<cdot>" ++ prettyPrint var ++ " \\<noteq> FF \\<Longrightarrow> "
-        assumes (App _ op var) | op ~= "isWHNF" = prettyPrint var ++ " \\<noteq> \\<bottom> \\<Longrightarrow> "
-        assumes _ = ""
-
-        exp1 = exp . transformBi unqual
-
-        -- Syntax translations
-        exp (App _ a b) = exp a ++ "\\<cdot>" ++ exp b
-        exp (Paren _ x) = "(" ++ exp x ++ ")"
-        exp (Var _ x) | Just x <- funs $ prettyPrint x = x
-        exp (Con _ (Special _ (TupleCon _ _ i))) = "\\<langle>" ++ replicate (i-1) ',' ++ "\\<rangle>"
-        exp (Con _ x) | Just x <- cons $ prettyPrint x = x
-        exp (Tuple _ _ xs) = "\\<langle>" ++ intercalate ", " (map exp xs) ++ "\\<rangle>"
-        exp (If _ a b c) = "If " ++ exp a ++ " then " ++ exp b ++ " else " ++ exp c
-        exp (Lambda _ xs y) = "\\<Lambda> " ++ unwords (map pat xs) ++ ". " ++ exp y
-        exp (InfixApp _ x op y) | Just op <- ops $ prettyPrint op =
-            if pre op then op ++ "\\<cdot>" ++ exp (paren x) ++ "\\<cdot>" ++ exp (paren y) else exp x ++ " " ++ op ++ " " ++ exp y
-
-        -- Translations from the Haskell 2010 report
-        exp (InfixApp l a (QVarOp _ b) c) = exp $ App l (App l (Var l b) a) c -- S3.4
-        exp x@(LeftSection l e op) = let v = fresh x in exp $ Paren l $ Lambda l [toNamed v] $ InfixApp l e op (toNamed v) -- S3.5
-        exp x@(RightSection l op e) = let v = fresh x in exp $ Paren l $ Lambda l [toNamed v] $ InfixApp l (toNamed v) op e -- S3.5
-        exp x = prettyPrint x
-
-        pat (PTuple _ _ xs) = "\\<langle>" ++ intercalate ", " (map pat xs) ++ "\\<rangle>"
-        pat x = prettyPrint x
-
-        fresh x = head $ ("z":["v" ++ show i | i <- [1..]]) \\ vars x
diff --git a/src/Test/Translate.hs b/src/Test/Translate.hs
deleted file mode 100644
--- a/src/Test/Translate.hs
+++ /dev/null
@@ -1,106 +0,0 @@
-
--- | Translate the hints to Haskell and run with GHC.
-module Test.Translate(testTypeCheck, testQuickCheck) where
-
-import Control.Monad
-import Control.Monad.IO.Class
-import Data.List.Extra
-import System.IO.Extra
-import Data.Maybe
-import System.Process
-import System.Exit
-import System.FilePath
-
-import Config.Type
-import HSE.All
-import Test.Util
-
-
-runMains :: FilePath -> FilePath -> [String] -> Test ()
-runMains datadir tmpdir xs = do
-    res <- liftIO $ (if tmpdir == "" then withTempDir else ($ tmpdir)) $ \dir -> do
-        ms <- forM (zip [1..] xs) $ \(i,x) -> do
-            let m = "I" ++ show i
-            writeFile (dir </> m <.> "hs") $ replace "module Main" ("module " ++ m) x
-            return m
-        writeFile (dir </> "Main.hs") $ unlines $
-            ["import qualified " ++ m | m <- ms] ++
-            ["main = do"] ++
-            ["    " ++ m ++ ".main" | m <- ms]
-        system $ "runhaskell -i" ++ dir ++ " -i" ++ datadir ++ " Main"
-    replicateM_ (length xs) $ tested $ res == ExitSuccess
-
-
--- | Given a set of hints, do all the HintRule hints type check
-testTypeCheck :: FilePath -> FilePath -> [[Setting]] -> Test ()
-testTypeCheck = wrap toTypeCheck
-
--- | Given a set of hints, do all the HintRule hints satisfy QuickCheck
-testQuickCheck :: FilePath -> FilePath -> [[Setting]] -> Test ()
-testQuickCheck = wrap toQuickCheck
-
-wrap :: ([HintRule] -> [String]) -> FilePath -> FilePath -> [[Setting]] -> Test ()
-wrap f datadir tmpdir hints = runMains datadir tmpdir [unlines $ body [x | SettingMatchExp x <- xs] | xs <- hints]
-    where
-        body xs =
-            ["{-# LANGUAGE NoMonomorphismRestriction, ExtendedDefaultRules, ScopedTypeVariables, DeriveDataTypeable #-}"
-            ,"{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}"
-            ,"module Main(main) where"] ++
-            concat [map (prettyPrint . hackImport) $ scopeImports $ hintRuleScope x | x <- take 1 xs] ++
-            f xs
-
-        -- Hack around haskell98 not being compatible with base anymore
-        hackImport i@ImportDecl{importAs=Just a,importModule=b}
-            | prettyPrint b `elem` words "Maybe List Monad IO Char" = i{importAs=Just b,importModule=a}
-        hackImport i = i
-
-
----------------------------------------------------------------------
--- TYPE CHECKING
-
-toTypeCheck :: [HintRule] -> [String]
-toTypeCheck hints =
-    ["import HLint_TypeCheck hiding(main)"
-    ,"main = return ()"] ++
-    ["{-# LINE " ++ show (startLine $ ann rhs) ++ " " ++ show (fileName $ ann rhs) ++ " #-}\n" ++
-     prettyPrint (PatBind an (toNamed $ "test" ++ show i) bod Nothing)
-    | (i, HintRule _ _ _ lhs rhs side _notes  _ghcScope  _ghcLhs _ghcRhs _ghcSide) <- zip [1..] hints, "noTypeCheck" `notElem` vars (maybeToList side)
-    , let vs = map toNamed $ nubOrd $ filter isUnifyVar $ vars lhs ++ vars rhs
-    , let inner = InfixApp an (Paren an lhs) (toNamed "==>") (Paren an rhs)
-    , let bod = UnGuardedRhs an $ if null vs then inner else Lambda an vs inner]
-
-
----------------------------------------------------------------------
--- QUICKCHECK
-
-toQuickCheck :: [HintRule] -> [String]
-toQuickCheck hints =
-    ["import HLint_QuickCheck hiding(main)"
-    ,"default(Maybe Bool,Int,Dbl)"
-    ,prettyPrint $ PatBind an (toNamed "main") (UnGuardedRhs an $ toNamed "withMain" $$ Do an tests) Nothing]
-    where
-        str x = Lit an $ String an x (show x)
-        int x = Lit an $ Int an (toInteger x) (show x)
-        app = App an
-        a $$ b = InfixApp an a (toNamed "$") b
-        tests =
-            [ Qualifier an $
-                Let an (BDecls an [PatBind an (toNamed "t") (UnGuardedRhs an bod) Nothing]) $
-                (toNamed "test" `app` str (fileName $ ann rhs) `app` int (startLine $ ann rhs) `app`
-                 str (prettyPrint lhs ++ " ==> " ++ prettyPrint rhs)) `app` toNamed "t"
-            | (i, HintRule _ _ _ lhs rhs side note _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zip [1..] hints, "noQuickCheck" `notElem` vars (maybeToList side)
-            , let vs = map (restrict side) $ nubOrd $ filter isUnifyVar $ vars lhs ++ vars rhs
-            , let op = if any isRemovesError note then "?==>" else "==>"
-            , let inner = InfixApp an (Paren an lhs) (toNamed op) (Paren an rhs)
-            , let bod = if null vs then Paren an inner else Lambda an vs inner]
-
-        restrict (Just side) v
-            | any (=~= App an (toNamed "isNegZero") (toNamed v)) (universe side) = PApp an (toNamed "NegZero") [toNamed v]
-            | any (=~= App an (toNamed "isNat") (toNamed v)) (universe side) = PApp an (toNamed "Nat") [toNamed v]
-            | any (=~= App an (toNamed "isCompare") (toNamed v)) (universe side) = PApp an (toNamed "Compare") [toNamed v]
-        restrict _ v = toNamed v
-
-
-isRemovesError :: Note -> Bool
-isRemovesError RemovesError{} = True
-isRemovesError _ = False
diff --git a/src/Test/Util.hs b/src/Test/Util.hs
--- a/src/Test/Util.hs
+++ b/src/Test/Util.hs
@@ -2,8 +2,7 @@
 
 module Test.Util(
     Test, withTests,
-    tested, passed, failed, progress,
-    addIdeas, getIdeas
+    passed, failed, progress,
     ) where
 
 import Idea
@@ -12,7 +11,6 @@
 import Control.Monad.IO.Class
 import Data.IORef
 
-
 data S = S
     {failures :: !Int
     ,total :: !Int
@@ -32,17 +30,7 @@
     putStrLn $ if failures == 0
         then "Tests passed (" ++ show total ++ ")"
         else "Tests failed (" ++ show failures ++ " of " ++ show total ++ ")"
-    return (failures, res)
-
-addIdeas :: [Idea] -> Test ()
-addIdeas xs = do
-    ref <- Test ask
-    liftIO $ modifyIORef' ref $ \s -> s{ideas = xs : ideas s}
-
-getIdeas :: Test [Idea]
-getIdeas = do
-    ref <- Test ask
-    liftIO $ concat . reverse . ideas <$> readIORef ref
+    pure (failures, res)
 
 progress :: Test ()
 progress = liftIO $ putChar '.'
@@ -57,6 +45,3 @@
     unless (null xs) $ liftIO $ putStrLn $ unlines $ "" : xs
     ref <- Test ask
     liftIO $ modifyIORef' ref $ \s -> s{total=total s+1, failures=failures s+1}
-
-tested :: Bool -> Test ()
-tested b = if b then passed else failed []
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
@@ -14,6 +15,7 @@
 import System.Console.CmdArgs.Verbosity
 import System.Time.Extra
 import System.IO.Unsafe
+import System.IO
 
 
 type Category = String
@@ -39,11 +41,13 @@
 timedIO :: Category -> Item -> IO a -> IO a
 timedIO c i x = if not useTimings then x else do
     let quiet = c == "Hint"
-    unless quiet $ whenLoud $ putStr $ "Performing " ++ c ++ " of " ++ i ++ "... "
+    unless quiet $ whenLoud $ do
+        putStr $ "# " ++ c ++ " of " ++ i ++ "... "
+        hFlush stdout
     (time, x) <- duration x
-    atomicModifyIORef' timings $ \mp -> (Map.insertWith (+) (c, i) time mp, ())
+    atomicModifyIORef'_ timings $ Map.insertWith (+) (c, i) time
     unless quiet $ whenLoud $ putStrLn $ "took " ++ showDuration time
-    return x
+    pure x
 
 startTimings :: IO ()
 startTimings = do
diff --git a/src/Util.hs b/src/Util.hs
--- a/src/Util.hs
+++ b/src/Util.hs
@@ -1,22 +1,20 @@
 {-# LANGUAGE ExistentialQuantification, Rank2Types #-}
 
 module Util(
-    parseExtensions,
-    configExtensions,
     forceList,
     gzip, universeParentBi,
     exitMessage, exitMessageImpure,
-    getContentsUTF8
+    getContentsUTF8, wildcardMatch
     ) where
 
-import Data.List
 import System.Exit
 import System.IO
 import System.IO.Unsafe
 import Unsafe.Coerce
 import Data.Data
-import Data.Generics.Uniplate.Operations
-import Language.Haskell.Exts.Extension
+import Data.Generics.Uniplate.DataOnly
+import System.FilePattern
+import Data.List.Extra
 
 
 ---------------------------------------------------------------------
@@ -60,37 +58,31 @@
 ---------------------------------------------------------------------
 -- DATA.GENERICS.UNIPLATE.OPERATIONS
 
-universeParent :: Uniplate a => a -> [(Maybe a, a)]
+universeParent :: Data a => a -> [(Maybe a, a)]
 universeParent x = (Nothing,x) : f x
     where
-        f :: Uniplate a => a -> [(Maybe a, a)]
+        f :: Data a => a -> [(Maybe a, a)]
         f x = concat [(Just x, y) : f y | y <- children x]
 
-universeParentBi :: Biplate a b => a -> [(Maybe b, b)]
+universeParentBi :: (Data a, Data b) => a -> [(Maybe b, b)]
 universeParentBi = concatMap universeParent . childrenBi
 
 
 ---------------------------------------------------------------------
--- LANGUAGE.HASKELL.EXTS.EXTENSION
-
--- | Extensions we turn on by default when parsing. Aim to parse as many files as we can.
-parseExtensions :: [Extension]
-parseExtensions = [e | e@EnableExtension{} <- knownExtensions] \\ map EnableExtension badExtensions
-
--- | Extensions we turn on when reading config files, don't have to deal with the whole world
---   of variations - in particular, we might require spaces in some places.
-configExtensions :: [Extension]
-configExtensions = [e | e@EnableExtension{} <- knownExtensions] \\ map EnableExtension reallyBadExtensions
-
-badExtensions = reallyBadExtensions ++
-    [Arrows -- steals proc
-    ,UnboxedTuples, UnboxedSums -- breaks (#) lens operator
-    ,QuasiQuotes -- breaks [x| ...], making whitespace free list comps break
-    ,DoRec, RecursiveDo -- breaks rec
-    ,TypeApplications -- HSE fails on @ patterns
-    ]
+-- SYSTEM.FILEPATTERN
 
-reallyBadExtensions =
-    [TransformListComp -- steals the group keyword
-    ,XmlSyntax, RegularPatterns -- steals a-b and < operators
-    ]
+-- | 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/bracket.test b/tests/bracket.test
new file mode 100644
--- /dev/null
+++ b/tests/bracket.test
@@ -0,0 +1,29 @@
+---------------------------------------------------------------------
+RUN tests/bracket-slice.hs
+FILE tests/bracket-slice.hs
+
+fAnd :: [a -> Bool] -> a -> Bool
+fAnd fs x = all ($x) fs
+
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/bracket-slice-spaced.hs
+FILE tests/bracket-slice-spaced.hs
+
+fAnd :: [a -> Bool] -> a -> Bool
+fAnd fs x = all ($ x) fs
+
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/bracket-slice-plus.hs
+FILE tests/bracket-slice-plus.hs
+
+incAll :: [Int] -> Int -> [Int]
+incAll ys x = map (+x) fs
+
+OUTPUT
+No hints
diff --git a/tests/cmdline.test b/tests/cmdline.test
new file mode 100644
--- /dev/null
+++ b/tests/cmdline.test
@@ -0,0 +1,29 @@
+---------------------------------------------------------------------
+RUN lint tests/cmdline-lint.hs
+FILE tests/cmdline-lint.hs
+
+foo = map f (map g xs)
+
+OUTPUT
+tests/cmdline-lint.hs:2:7-22: Suggestion: Use map once
+Found:
+  map f (map g xs)
+Perhaps:
+  map (f . g) xs
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/cmdline-bare.hs
+FILE tests/cmdline-bare.hs
+
+foo = map f (map g xs)
+
+OUTPUT
+tests/cmdline-bare.hs:2:7-22: Suggestion: Use map once
+Found:
+  map f (map g xs)
+Perhaps:
+  map (f . g) xs
+
+1 hint
diff --git a/tests/cpp.test b/tests/cpp.test
new file mode 100644
--- /dev/null
+++ b/tests/cpp.test
@@ -0,0 +1,167 @@
+---------------------------------------------------------------------
+RUN tests/cpp-full.hs
+FILE tests/cpp-full.hs
+/* this is a test */
+
+main = putStrLn $ show "Hello"
+OUTPUT
+tests/cpp-full.hs:3:8-30: Warning: Use print
+Found:
+  putStrLn $ show "Hello"
+Perhaps:
+  print "Hello"
+
+1 hint
+
+---------------------------------------------------------------------
+RUN -XNoCPP tests/cpp-none.hs
+FILE tests/cpp-none.hs
+#include "Any/File.h"
+
+main = print "Hello"
+EXIT 0
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN -XNoCPP tests/cpp-must-not-run.hs
+FILE tests/cpp-must-not-run.hs
+{-
+#error Cpp has run
+-}
+main = undefined
+EXIT 0
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN --cpp-define FOO tests/cpp-ext-enable.hs
+FILE tests/cpp-ext-enable.hs
+{-# LANGUAGE CPP #-}
+#if defined(FOO)
+{-# LANGUAGE Foo #-}
+#endif
+main = undefined
+EXIT 1
+OUTPUT
+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 #-}
+
+  {-# LANGUAGE Foo #-}
+
+  main = undefined
+
+1 error
+
+---------------------------------------------------------------------
+RUN tests/cpp-ext-disable.hs
+FILE tests/cpp-ext-disable.hs
+{-# LANGUAGE CPP #-}
+#if defined(FOO)
+{-# LANGUAGE Foo #-}
+#endif
+main = undefined
+EXIT 1
+OUTPUT
+tests/cpp-ext-disable.hs:1:1-20: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE CPP #-}
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN --cpp-simple tests/cpp-simple.hs
+FILE tests/cpp-simple.hs
+#include "Any/File.h"
+
+main = print "Hello"
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/cpp-file1.hs
+FILE tests/cpp-file1.hs
+import Network.Wai
+#if MIN_VERSION_wai(2, 0, 0)
+import Network.Wai.Internal
+#endif
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN --cpp-file=tests/cabal_macros.h tests/cpp-file2.hs
+FILE tests/cabal_macros.h
+#define MIN_VERSION_wai(a,b,c) 1
+FILE tests/cpp-file2.hs
+import Network.Wai
+#if MIN_VERSION_wai(2, 0, 0)
+import Network.Wai.Internal
+#endif
+foo = map f . map g
+OUTPUT
+tests/cpp-file2.hs:5:7-19: Suggestion: Use map once
+Found:
+  map f . map g
+Perhaps:
+  map (f . g)
+
+1 hint
+
+---------------------------------------------------------------------
+RUN --cpp-simple tests/cpp-file3.hs
+FILE tests/cpp-file3.hs
+import Network.Wai
+#if MIN_VERSION_wai(2, 0, 0)
+import Network.Wai.Internal
+#endif
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN --cpp-simple tests/cpp-file4.hs
+FILE tests/cpp-file4.hs
+import Network.Wai
+#if defined(MIN_VERSION_wai) && MIN_VERSION_wai(2, 0, 0)
+import Network.Wai.Internal
+#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/cross.test b/tests/cross.test
new file mode 100644
--- /dev/null
+++ b/tests/cross.test
@@ -0,0 +1,25 @@
+---------------------------------------------------------------------
+RUN tests/cross.hs1 tests/cross.hs2 --cross
+FILE tests/cross.hs1
+
+module B(bar) where
+
+
+bar = 1
+    where
+        a = 1
+        b = 2
+        c = 3
+
+FILE tests/cross.hs2
+
+module A(foo) where
+
+foo = 1
+    where
+        a = 1
+        b = 2
+        c = 3
+
+OUTPUT
+No hints
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/find.test b/tests/find.test
new file mode 100644
--- /dev/null
+++ b/tests/find.test
@@ -0,0 +1,42 @@
+---------------------------------------------------------------------
+RUN --find=tests/find.hs
+FILE tests/find.hs
+{-# LANGUAGE CPP, ExistentialQuantification, Rank2Types #-}
+
+module Util where
+
+import Control.Arrow
+import Control.Monad.Trans.State
+import Data.Char
+import Data.Function
+
+infixr 4 %^&, `wheeeee`
+
+instance Foo a where
+    bar = baz + qux
+
+listM' :: Monad m => [a] -> m [a]
+listM' x = length x `seq` return x
+
+notNull = not . null
+
+headDef :: a -> [a] -> a
+headDef x [] = x
+headDef x (y:ys) = y
+
+isLeft Left{} = True; isLeft _ = False
+isRight = not . isLeft
+
+swap :: (a,b) -> (b,a)
+swap (a,b) = (b,a)
+
+defaultExtensions = knownExtensions \\ badExtensions
+OUTPUT
+# hints found in tests/find.hs
+- fixity: "infixr 4 %^&"
+- fixity: "infixr 4 `wheeeee`"
+- warn: {lhs: "baz + qux", rhs: "bar"}
+- warn: {lhs: "length a `seq` return a", rhs: "listM' a"}
+- warn: {lhs: "not (null a)", rhs: "notNull a"}
+- warn: {lhs: "not (isLeft a)", rhs: "isRight a"}
+- warn: {lhs: "knownExtensions \\\\ badExtensions", rhs: "defaultExtensions"}
diff --git a/tests/flag-extension.test b/tests/flag-extension.test
new file mode 100644
--- /dev/null
+++ b/tests/flag-extension.test
@@ -0,0 +1,31 @@
+---------------------------------------------------------------------
+RUN tests/directory
+FILE tests/directory/File1.hs
+ foo = map f . map g
+FILE tests/directory/File2.lhs
+> foo = map f . map g
+OUTPUT
+tests/directory/File1.hs:1:8-20: Suggestion: Use map once
+Found:
+  map f . map g
+Perhaps:
+  map (f . g)
+
+tests/directory/File2.lhs:1:9-21: Suggestion: Use map once
+Found:
+  map f . map g
+Perhaps:
+  map (f . g)
+
+2 hints
+
+---------------------------------------------------------------------
+RUN tests/directory --extension=lhs
+OUTPUT
+tests/directory/File2.lhs:1:9-21: Suggestion: Use map once
+Found:
+  map f . map g
+Perhaps:
+  map (f . g)
+
+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
new file mode 100644
--- /dev/null
+++ b/tests/flag-no-summary.test
@@ -0,0 +1,18 @@
+---------------------------------------------------------------------
+RUN tests/flag-no-summary1.hs --no-summary
+FILE tests/flag-no-summary1.hs
+main = map f $ map g xs
+OUTPUT
+tests/flag-no-summary1.hs:1:8-23: Suggestion: Use map once
+Found:
+  map f $ map g xs
+Perhaps:
+  map (f . g) xs
+
+EXIT 1
+---------------------------------------------------------------------
+RUN tests/flag-no-summary2.hs --no-summary
+FILE tests/flag-no-summary2.hs
+main = pure ()
+OUTPUT
+EXIT 0
diff --git a/tests/flag-only.test b/tests/flag-only.test
new file mode 100644
--- /dev/null
+++ b/tests/flag-only.test
@@ -0,0 +1,30 @@
+---------------------------------------------------------------------
+RUN tests/flag-only-one-arg.hs --only="Redundant bracket"
+FILE tests/flag-only-one-arg.hs
+foo xs = if length xs == 0 then 42 else (666) -- should result in 2 suggestions
+OUTPUT
+tests/flag-only-one-arg.hs:1:41-45: Warning: Redundant bracket
+Found:
+  (666)
+Perhaps:
+  666
+
+1 hint
+---------------------------------------------------------------------
+RUN tests/flag-only-many-args.hs --only="Redundant bracket" --only="Use ++"
+FILE tests/flag-only-many-args.hs
+foo xs = if length xs == 0 then concat ["foo", "bar"] else (666) -- should result in 3 suggestions
+OUTPUT
+tests/flag-only-many-args.hs:1:33-53: Suggestion: Use ++
+Found:
+  concat ["foo", "bar"]
+Perhaps:
+  "foo" ++ "bar"
+
+tests/flag-only-many-args.hs:1:60-64: Warning: Redundant bracket
+Found:
+  (666)
+Perhaps:
+  666
+
+2 hints
diff --git a/tests/flag-quiet.test b/tests/flag-quiet.test
new file mode 100644
--- /dev/null
+++ b/tests/flag-quiet.test
@@ -0,0 +1,6 @@
+---------------------------------------------------------------------
+RUN tests/flag-quiet.hs --quiet
+FILE tests/flag-quiet.hs
+main = map f $ map g xs
+OUTPUT
+EXIT 1
diff --git a/tests/flag-with-group.test b/tests/flag-with-group.test
new file mode 100644
--- /dev/null
+++ b/tests/flag-with-group.test
@@ -0,0 +1,22 @@
+---------------------------------------------------------------------
+FILE tests/flag-with-group.hs
+foo = map (+1) . maybe mempty reverse
+RUN "--with-group=generalise" tests/flag-with-group.hs
+OUTPUT
+tests/flag-with-group.hs:1:7-9: Warning: Use fmap
+Found:
+  map
+Perhaps:
+  fmap
+
+1 hint
+---------------------------------------------------------------------
+RUN "--with-group=generalise-for-conciseness" tests/flag-with-group.hs
+OUTPUT
+tests/flag-with-group.hs:1:18-29: Warning: Use foldMap
+Found:
+  maybe mempty
+Perhaps:
+  foldMap
+
+1 hint
diff --git a/tests/hint.test b/tests/hint.test
new file mode 100644
--- /dev/null
+++ b/tests/hint.test
@@ -0,0 +1,241 @@
+---------------------------------------------------------------------
+RUN tests/newtype-derive.hs --hint=data/hlint.yaml
+FILE tests/newtype-derive.hs
+{-# LANGUAGE DeriveTraversable #-} -- Implies DeriveFoldable and DeriveFunctor
+{-# LANGUAGE DeriveDataTypeable #-}
+module Test(A) where
+
+import Data.Foldable (Foldable)
+import Data.Traversable (Traversable)
+import Data.Typeable (Typeable)
+
+newtype A f = A f
+  deriving (Foldable, Functor, Traversable, Typeable)
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/note.hs
+FILE tests/note.hs
+{-# LANGUAGE RecordWildCards #-}
+
+module Sample(test) where
+
+test xs = length xs == 0
+OUTPUT
+tests/note.hs:1:1-32: Warning: Unused LANGUAGE pragma
+Found:
+  {-# LANGUAGE RecordWildCards #-}
+Perhaps you should remove it.
+Note: may require `{-# LANGUAGE DisambiguateRecordFields #-}` adding to the top of the file
+
+tests/note.hs:5:11-24: Suggestion: Use null
+Found:
+  length xs == 0
+Perhaps:
+  null xs
+Note: increases laziness
+
+2 hints
+
+---------------------------------------------------------------------
+RUN tests/brackets.hs
+FILE tests/brackets.hs
+test = if isNothing x then (-1.0) else fromJust x
+OUTPUT
+tests/brackets.hs:1:8-49: Warning: Use fromMaybe
+Found:
+  if isNothing x then (- 1.0) else fromJust x
+Perhaps:
+  fromMaybe (- 1.0) x
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/typesig-ignore.hs
+FILE tests/typesig-ignore.hs
+-- Bug #563
+module Foo(foobar) where
+{-# ANN foobar "HLint: ignore Use String" #-}
+
+foobar :: [Char]
+foobar = []
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/typesig-ignore2.hs
+FILE tests/typesig-ignore2.hs
+-- Bug #563
+module Foo(foobar) where
+{-# HLINT ignore foobar "Use String" #-}
+
+foobar :: [Char]
+foobar = []
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/restricted-module.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-module.lhs
+> import Restricted.Module
+OUTPUT
+tests/restricted-module.lhs:1:3-26: Warning: Avoid restricted module
+Found:
+  import Restricted.Module
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-module-message.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-module-message.lhs
+> import Restricted.Module.Message
+OUTPUT
+tests/restricted-module-message.lhs:1:3-34: Warning: Avoid restricted module
+Found:
+  import Restricted.Module.Message
+Note: Custom message
+
+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 ()
+OUTPUT
+tests/restricted-function.lhs:1:10-19: Warning: Avoid restricted function
+Found:
+  restricted
+Note: may break the code
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-function-message.lhs --hint=data/test-restrict.yaml
+FILE tests/restricted-function-message.lhs
+> main = restrictedMessage ()
+OUTPUT
+tests/restricted-function-message.lhs:1:10-26: Warning: Avoid restricted function
+Found:
+  restrictedMessage
+Note: Custom message
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/restricted-extension.hs --hint=data/test-restrict.yaml
+FILE tests/restricted-extension.hs
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+module Test(A) where
+
+import Data.Foldable (Foldable)
+import Data.Traversable (Traversable)
+import Data.Typeable (Typeable)
+
+newtype A f = A f
+  deriving (Foldable, Functor, Traversable, Typeable)
+OUTPUT
+OUTPUT
+tests/restricted-extension.hs:1:1-31: Warning: Unused LANGUAGE pragma
+Found:
+  {-# LANGUAGE DeriveFoldable #-}
+Perhaps you should remove it.
+Note: Extension DeriveFoldable is implied by DeriveTraversable
+
+tests/restricted-extension.hs:2:1-30: Warning: Unused LANGUAGE pragma
+Found:
+  {-# LANGUAGE DeriveFunctor #-}
+Perhaps you should remove it.
+Note: Extension DeriveFunctor is implied by DeriveTraversable
+
+tests/restricted-extension.hs:2:1-30: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE DeriveFunctor #-}
+Note: may break the code
+
+tests/restricted-extension.hs:3:1-34: Warning: Avoid restricted extensions
+Found:
+  {-# LANGUAGE DeriveTraversable #-}
+Note: Custom message
+
+4 hints
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
new file mode 100644
--- /dev/null
+++ b/tests/json.test
@@ -0,0 +1,40 @@
+---------------------------------------------------------------------
+RUN tests/json-none.hs --json
+FILE tests/json-none.hs
+foo = (+1)
+OUTPUT
+[]
+
+---------------------------------------------------------------------
+RUN tests/json-one.hs --json
+FILE tests/json-one.hs
+foo = (+1)
+bar x = foo x
+OUTPUT
+[{"module":["Main"],"decl":["bar"],"severity":"Warning","hint":"Eta reduce","file":"tests/json-one.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\"}]"}]
+
+---------------------------------------------------------------------
+RUN tests/json-two.hs --json
+FILE tests/json-two.hs
+foo = (+1)
+bar x = foo x
+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":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
+FILE tests/json-parse-error.hs
+@
+OUTPUT
+[{"module":[],"decl":[],"severity":"Error","hint":"Parse error: on input `@'","file":"tests/json-parse-error.hs","startLine":1,"startColumn":1,"endLine":1,"endColumn":2,"from":"> @\n","to":null,"note":[],"refactorings":"[]"}]
+
+---------------------------------------------------------------------
+RUN tests/json-note.hs --json
+FILE tests/json-note.hs
+foo = any (a ==)
+bar = foldl (&&) True
+OUTPUT
+[{"module":["Main"],"decl":["foo"],"severity":"Warning","hint":"Use elem","file":"tests/json-note.hs","startLine":1,"startColumn":7,"endLine":1,"endColumn":17,"from":"any (a ==)","to":"elem a","note":["requires a valid `Eq` instance for `a`"],"refactorings":"[Replace {rtype = Expr, pos = SrcSpan {startLine = 1, startCol = 7, endLine = 1, endCol = 17}, subts = [(\"a\",SrcSpan {startLine = 1, startCol = 12, endLine = 1, endCol = 13})], orig = \"elem a\"}]"}
+,{"module":["Main"],"decl":["bar"],"severity":"Warning","hint":"Use and","file":"tests/json-note.hs","startLine":2,"startColumn":7,"endLine":2,"endColumn":22,"from":"foldl (&&) True","to":"and","note":["increases laziness"],"refactorings":"[Replace {rtype = Expr, pos = SrcSpan {startLine = 2, startCol = 7, endLine = 2, endCol = 22}, subts = [], orig = \"and\"}]"}]
diff --git a/tests/lhs.test b/tests/lhs.test
new file mode 100644
--- /dev/null
+++ b/tests/lhs.test
@@ -0,0 +1,47 @@
+---------------------------------------------------------------------
+RUN tests/lhs-line-numbers.lhs
+FILE tests/lhs-line-numbers.lhs
+BUG 331
+
+> main = print ([1] ++ [2, 3])
+OUTPUT
+tests/lhs-line-numbers.lhs:3:17-29: Suggestion: Use :
+Found:
+  [1] ++ [2, 3]
+Perhaps:
+  1 : [2, 3]
+
+1 hint
+
+---------------------------------------------------------------------
+RUN tests/lhs-line-numbers2.lhs
+FILE tests/lhs-line-numbers2.lhs
+BUG 331
+%  Blah1
+%  Blah2
+%  Blah3
+
+\begin{code}
+module
+\end{code}
+OUTPUT
+tests/lhs-line-numbers2.lhs:10:1: Error: Parse error: possibly incorrect indentation or mismatched brackets
+Found:
+    \end{code}
+
+  >
+
+1 error
+
+---------------------------------------------------------------------
+RUN tests/lhs-first-line.lhs
+FILE tests/lhs-first-line.lhs
+> main = print ([1] ++ [2, 3])
+OUTPUT
+tests/lhs-first-line.lhs:1:17-29: Suggestion: Use :
+Found:
+  [1] ++ [2, 3]
+Perhaps:
+  1 : [2, 3]
+
+1 hint
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
new file mode 100644
--- /dev/null
+++ b/tests/parse-error.test
@@ -0,0 +1,33 @@
+---------------------------------------------------------------------
+RUN "--ignore=Parse error" tests/ignore-parse-error.hs
+FILE tests/ignore-parse-error.hs
+where
+OUTPUT
+No hints
+
+---------------------------------------------------------------------
+RUN tests/ignore-parse-error2.hs
+FILE tests/ignore-parse-error2.hs
+module Foo where
+
+where
+OUTPUT
+tests/ignore-parse-error2.hs:3:1-5: Error: Parse error: on input `where'
+Found:
+    module Foo where
+
+  > where
+
+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: tests/ignore-parse-error3.hs:1:14: error: [GHC-46537]
+    Unsupported extension: InvalidExtension
+Found:
+  {-# LANGUAGE InvalidExtension #-}
+
+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
new file mode 100644
--- /dev/null
+++ b/tests/serialise.test
@@ -0,0 +1,85 @@
+---------------------------------------------------------------------
+RUN tests/serialise-none.hs --serialise
+FILE tests/serialise-none.hs
+foo = (+1)
+OUTPUT
+[]
+
+---------------------------------------------------------------------
+RUN tests/serialise-one.hs --serialise
+FILE tests/serialise-one.hs
+foo = (+1)
+bar x = foo x
+OUTPUT
+[("tests/serialise-one.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"}])]
+
+---------------------------------------------------------------------
+RUN tests/serialise-two.hs --serialise
+FILE tests/serialise-two.hs
+foo = (+1)
+bar x = foo x
+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-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"}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-three.hs --serialise
+FILE tests/serialise-three.hs
+foo = concat (map f (let x = x in x))
+OUTPUT
+[("tests/serialise-three.hs:1:7-37: Warning: Use concatMap\nFound:\n  concat (map f (let x = x in x))\nPerhaps:\n  concatMap f (let x = x in x)\n",[Replace {rtype = Expr, pos = SrcSpan {startLine = 1, startCol = 7, endLine = 1, endCol = 38}, subts = [("f",SrcSpan {startLine = 1, startCol = 19, endLine = 1, endCol = 20}),("x",SrcSpan {startLine = 1, startCol = 21, endLine = 1, endCol = 37})], orig = "concatMap f x"}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-four.hs --serialise --hint=data/hlint.yaml
+FILE tests/serialise-four.hs
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE CPP #-}
+OUTPUT
+[("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 = ""}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-five.hs --serialise
+FILE tests/serialise-five.hs
+import qualified GHC as GHC
+OUTPUT
+[("tests/serialise-five.hs:1:1-27: Suggestion: Redundant as\nFound:\n  import qualified GHC as GHC\nPerhaps:\n  import qualified GHC\n",[RemoveAsKeyword {pos = SrcSpan {startLine = 1, startCol = 1, endLine = 1, endCol = 28}}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-six.hs --serialise
+FILE tests/serialise-six.hs
+foo = qux (\x -> f (g x))
+OUTPUT
+[("tests/serialise-six.hs:1:12-24: Suggestion: Avoid lambda\nFound:\n  / x -> f (g x)\nPerhaps:\n  f . g\n",[Replace {rtype = Expr, pos = SrcSpan {startLine = 1, startCol = 12, endLine = 1, endCol = 25}, subts = [("a",SrcSpan {startLine = 1, startCol = 18, endLine = 1, endCol = 19}),("b",SrcSpan {startLine = 1, startCol = 21, endLine = 1, endCol = 22})], orig = "a . b"}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-seven.hs --serialise
+FILE tests/serialise-seven.hs
+foo = if baz
+        then qux
+        else if baz'
+             then qux'
+             else qux''
+OUTPUT
+[("tests/serialise-seven.hs:(1,1)-(5,23): Suggestion: Use guards\nFound:\n  foo = if baz then qux else if baz' then qux' else qux''\nPerhaps:\n  foo\n    | baz = qux\n    | baz' = qux'\n    | otherwise = qux''\n",[Replace {rtype = Match, pos = SrcSpan {startLine = 1, startCol = 1, endLine = 5, endCol = 24}, subts = [("g1001",SrcSpan {startLine = 1, startCol = 10, endLine = 1, endCol = 13}),("g1002",SrcSpan {startLine = 3, startCol = 17, endLine = 3, endCol = 21}),("e1001",SrcSpan {startLine = 2, startCol = 14, endLine = 2, endCol = 17}),("e1002",SrcSpan {startLine = 4, startCol = 19, endLine = 4, endCol = 23}),("e1003",SrcSpan {startLine = 5, startCol = 19, endLine = 5, endCol = 24})], orig = "foo\n  | g1001 = e1001\n  | g1002 = e1002\n  | otherwise = e1003"}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-eight.hs --serialise
+FILE tests/serialise-eight.hs
+foo = do x <- baz
+         x
+OUTPUT
+[("tests/serialise-eight.hs:(1,7)-(2,10): Warning: Use join\nFound:\n  do x <- baz\n     x\nPerhaps:\n  do join baz\n",[Replace {rtype = Stmt, pos = SrcSpan {startLine = 1, startCol = 10, endLine = 1, endCol = 18}, subts = [("x",SrcSpan {startLine = 1, startCol = 15, endLine = 1, endCol = 18})], orig = "join x"},Delete {rtype = Stmt, pos = SrcSpan {startLine = 2, startCol = 10, endLine = 2, endCol = 11}}])]
+
+
+---------------------------------------------------------------------
+RUN tests/serialise-nine.hs --serialise
+FILE tests/serialise-nine.hs
+foo | True = baz
+OUTPUT
+[("tests/serialise-nine.hs:1:1-16: Suggestion: Redundant guard\nFound:\n  foo | True = baz\nPerhaps:\n  foo = baz\n",[Delete {rtype = Stmt, pos = SrcSpan {startLine = 1, startCol = 7, endLine = 1, endCol = 11}}])]
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
