diff --git a/data/Default.hs b/data/Default.hs
--- a/data/Default.hs
+++ b/data/Default.hs
@@ -2,6 +2,7 @@
 module HLint.Default where
 
 import Control.Arrow
+import Control.Exception
 import Control.Monad
 import Data.Function
 import Data.Int
@@ -30,12 +31,12 @@
 
 -- ORD
 
-error = not (a == b) ==> a /= b
-error = not (a /= b) ==> a == b
-error = not (a >  b) ==> a <= b
-error = not (a >= b) ==> a <  b
-error = not (a <  b) ==> a >= b
-error = not (a <= b) ==> a >  b
+error = not (a == b) ==> a /= b where note = "incorrect if either value is NaN"
+error = not (a /= b) ==> a == b where note = "incorrect if either value is NaN"
+error = not (a >  b) ==> a <= b where note = "incorrect if either value is NaN"
+error = not (a >= b) ==> a <  b where note = "incorrect if either value is NaN"
+error = not (a <  b) ==> a >= b where note = "incorrect if either value is NaN"
+error = not (a <= b) ==> a >  b where note = "incorrect if either value is NaN"
 error = compare x y /= GT ==> x <= y
 error = compare x y == LT ==> x < y
 error = compare x y /= LT ==> x >= y
@@ -74,7 +75,7 @@
 error = and (map p x) ==> all p x
 error = zipWith (,) ==> zip
 error = zipWith3 (,,) ==> zip3
-warn  = length x == 0 ==> null x
+warn  = length x == 0 ==> null x where note = "increases laziness"
 warn  "Use null" = length x /= 0 ==> not (null x)
 error "Use :" = (\x -> [x]) ==> (:[])
 error = map (uncurry f) (zip x y) ==> zipWith f x y
@@ -103,8 +104,8 @@
 error = (\x -> x) ==> id
 error = (\(_,y) -> y) ==> snd
 error = (\(x,_) -> x) ==> fst
-error = (\x y-> f (x,y)) ==> curry f where _ = notIn [x,y] f
-error = (\(x,y) -> f x y) ==> uncurry f where _ = notIn [x,y] f
+warn "Use curry" = (\x y-> f (x,y)) ==> curry f where _ = notIn [x,y] f
+warn "Use uncurry" = (\(x,y) -> f x y) ==> uncurry f where _ = notIn [x,y] f
 error "Redundant $" = (($) . f) ==> f
 error "Redundant $" = (f $) ==> f
 warn  = (\x -> y) ==> const y where _ = isAtom y && notIn x y
@@ -147,8 +148,10 @@
 error "Monad law, left identity" = return a >>= f ==> f a
 error "Monad law, right identity" = m >>= return ==> m
 warn  = m >>= return . f ==> fmap f m
-error = (if x then y else return ()) ==> Control.Monad.when x $ _noParen_ y
-error = (if x then return () else y) ==> Control.Monad.unless x $ _noParen_ y
+error = (if x then y else return ()) ==> Control.Monad.when x $ _noParen_ y where _ = not (isAtom y)
+error = (if x then y else return ()) ==> Control.Monad.when x y where _ = isAtom y
+error = (if x then return () else y) ==> Control.Monad.unless x $ _noParen_ y where _ = not (isAtom y)
+error = (if x then return () else y) ==> Control.Monad.unless x y where _ = isAtom y
 error = sequence (map f x) ==> mapM f x
 error = sequence_ (map f x) ==> mapM_ f x
 warn  = flip mapM ==> Control.Monad.forM
@@ -207,6 +210,8 @@
 
 -- MATHS
 
+error "Redundant fromIntegral" = fromIntegral x ==> x where _ = isLitInt x
+error "Redundant fromInteger" = fromInteger x ==> x where _ = isLitInt x
 warn  = x + negate y ==> x - y
 warn  = 0 - x ==> negate x
 warn  = log y / log x ==> logBase x y
@@ -217,8 +222,19 @@
 warn  = n `rem` 2 /= 0 ==> odd n
 warn  = not (even x) ==> odd x
 warn  = not (odd x) ==> even x
+warn  = x ** 0.5 ==> sqrt x
+warn  = x ^^ y ==> x ** y where _ = isLitInt y
 warn  "Use 1" = x ^ 0 ==> 1
 
+-- EXCEPTION
+
+error "Use Control.Exception.catch" = Prelude.catch ==> Control.Exception.catch where note = "Prelude.catch does not catch most exceptions"
+warn = flip Control.Exception.catch ==> handle
+warn = flip (catchJust p) ==> handleJust p
+warn = Control.Exception.bracket b (const a) (const t) ==> Control.Exception.bracket_ b a t
+warn = Control.Exception.bracket (openFile x y) hClose ==> withFile x y
+warn = Control.Exception.bracket (openBinaryFile x y) hClose ==> withBinaryFile x y
+
 -- EVALUATE
 
 -- TODO: These should be moved in to HSE\Evaluate.hs and applied
@@ -268,7 +284,10 @@
     where _ = (isAtom f || isApp f) && notIn a g
 -}
 
+test = hints named test are to allow people to put test code within hint files
+testPrefix = and any prefix also works
 
+
 {-
 <TEST>
 yes = concat . map f -- concatMap f
@@ -301,6 +320,7 @@
     -- if nullPS s || (headPS s /= '\n') then return False else alter_input tailPS >> return True
 yes = if foo then do stuff; moreStuff; lastOfTheStuff else return () \
     -- Control.Monad.when foo $ do stuff ; moreStuff ; lastOfTheStuff
+yes = if foo then stuff else return () -- Control.Monad.when foo stuff
 yes = foo $ \(a, b) -> (a, y + b) -- Control.Arrow.second ((+) y)
 no  = foo $ \(a, b) -> (a, a + b)
 yes = map (uncurry (+)) $ zip [1 .. 5] [6 .. 10] -- zipWith (+) [1 .. 5] [6 .. 10]
@@ -324,7 +344,7 @@
 yes x = case x of {False -> a ; _ -> b} -- if x then b else a
 no = const . ok . toResponse $ "saved"
 yes = case x z of Nothing -> y z; Just pattern -> pattern -- fromMaybe (y z) (x z)
-yes = if p then s else return () -- Control.Monad.when p $ s
+yes = if p then s else return () -- Control.Monad.when p s
 error = a $$$$ b $$$$ c ==> a . b $$$$$ c
 yes = when (not . null $ asdf) -- unless (null asdf)
 yes = id 1 -- 1
@@ -333,6 +353,14 @@
 yes = [v | v <- xs] -- xs
 no  = [Left x | Left x <- xs]
 yes = Map.union a b -- a `Map.union` b
+when p s = if p then s else return ()
+yes = x ^^ 18 -- x ** 18
+no = x ^^ 18.5
+instance Arrow (->) where first f = f *** id
+yes = fromInteger 12 -- 12
+yes = catch -- Control.Exception.catch
+import Prelude hiding (catch); no = catch
+import Control.Exception as E; no = E.catch
 
 import Prelude \
 yes = flip mapM -- Control.Monad.forM
diff --git a/data/report_template.html b/data/report_template.html
--- a/data/report_template.html
+++ b/data/report_template.html
@@ -112,6 +112,7 @@
 
 #leftbar ul {margin-top: 0px; padding-left: 15px;}
 #leftbar p {margin-bottom: 0px;}
+.note {color: gray; font-size: smaller;}
 
 pre {
 	font-family: "lucida console", monospace;
diff --git a/hlint.cabal b/hlint.cabal
--- a/hlint.cabal
+++ b/hlint.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.6
 build-type:         Simple
 name:               hlint
-version:            1.8.7
+version:            1.8.8
 -- license is GPL v2 only
 license:            GPL
 license-file:       LICENSE
@@ -64,6 +64,7 @@
         HSE.Util
         Hint.All
         Hint.Bracket
+        Hint.Duplicate
         Hint.Extensions
         Hint.Import
         Hint.Lambda
diff --git a/hlint.htm b/hlint.htm
--- a/hlint.htm
+++ b/hlint.htm
@@ -80,7 +80,7 @@
 <h3 id="limitations">Bugs and limitations</h3>
 
 <p>
-	To report a bug either <a href="http://community.haskell.org/~ndm/contact/">email me</a>, or add the issue directly to <a href="http://code.google.com/p/ndmitchell/issues/list?q=proj:HLint">the bug tracker</a>. There are two common issues that I do not intend to fix:
+	To report a bug either <a href="http://community.haskell.org/~ndm/contact/">email me</a>, or add the issue directly to <a href="http://code.google.com/p/ndmitchell/issues/list?q=proj:HLint">the bug tracker</a>. There are three common issues that I do not intend to fix:
 </p>
 <ul>
 	<li>The presence of <tt>seq</tt> may cause some hints (i.e. eta-reduction) to change the semantics of a program.</li>
@@ -137,7 +137,7 @@
 <h3>Language Extensions</h3>
 
 <p>
-	HLint enables most Haskell extensions, disabling only those which steal too much syntax (currently Arrows, TransformListComp, XmlSyntax and RegularPatterns). Extensions can be enabled with <tt>-XArrows</tt>, and disabled with <tt>-XNoMagicHash</tt>. The flag <tt>-XHaskell98</tt> selects Haskell 98 compatibility.
+	HLint enables most Haskell extensions, disabling only those which steal too much syntax (currently Arrows, TransformListComp, XmlSyntax and RegularPatterns). Individual extensions can be enabled or disabled with, for instance, <tt>-XArrows</tt>, or <tt>-XNoMagicHash</tt>. The flag <tt>-XHaskell98</tt> selects Haskell 98 compatibility.
 </p>
 
 <h3 id="emacs">Emacs Integration</h3>
@@ -166,7 +166,7 @@
 	If your version of GHC does not support the GHC threaded runtime then install with the command: <tt>cabal install --flags="-threaded"</tt>
 </p>
 
-<h3>C preprocesor support</h3>
+<h3>C preprocessor support</h3>
 
 <p>
 	HLint runs the <a href="http://hackage.haskell.org/package/cpphs">cpphs C preprocessor</a> over all input files, by default using the current directory as the include path with no defined macros. These settings can be modified using the flags <tt>--cpp-include</tt> and <tt>--cpp-define</tt>. To disable the C preprocessor use the flag <tt>-XNoCPP</tt>. There are a number of limitations to the C preprocessor support:
diff --git a/src/CmdLine.hs b/src/CmdLine.hs
--- a/src/CmdLine.hs
+++ b/src/CmdLine.hs
@@ -26,7 +26,7 @@
 -- FIXME: Hints vs GivenHints is horrible
 data Cmd = Cmd
     {cmdTest :: Bool                 -- ^ run in test mode?
-    ,cmdFiles :: [FilePath]          -- ^ which files to run it on
+    ,cmdFiles :: Maybe [FilePath]    -- ^ which files to run it on, nothing = none given
     ,cmdHintFiles :: [FilePath]      -- ^ which settingsfiles to use
     ,cmdGivenHints :: [FilePath]     -- ^ which settignsfiles were explicitly given
     ,cmdReports :: [FilePath]        -- ^ where to generate reports
@@ -102,7 +102,7 @@
 
     let exts = [x | Ext x <- opt]
         exts2 = if null exts then ["hs","lhs"] else exts
-    files <- concatMapM (getFile exts2) files
+    files <- if null files then return Nothing else fmap Just $ concatMapM (getFile exts2) files
     findHints <- concatMapM (getFile exts2) [x | FindHints x <- opt]
 
     let hintFiles = [x | Hints x <- opt]
diff --git a/src/HLint.hs b/src/HLint.hs
--- a/src/HLint.hs
+++ b/src/HLint.hs
@@ -4,6 +4,7 @@
 
 import Control.Monad
 import Data.List
+import Data.Maybe
 
 import CmdLine
 import Settings
@@ -48,10 +49,12 @@
     let flags = parseFlags{cppFlags=cmdCpp, encoding=cmdEncoding, language=cmdLanguage}
     if cmdTest then
         test (\x -> hlint x >> return ()) cmdDataDir cmdGivenHints >> return []
-     else if null cmdFiles && notNull cmdFindHints then
+     else if isNothing cmdFiles && notNull cmdFindHints then
         mapM_ (\x -> putStrLn . fst =<< findSettings flags x) cmdFindHints >> return []
-     else if null cmdFiles then
+     else if isNothing cmdFiles then
         exitWithHelp
+     else if cmdFiles == Just [] then
+        error "No files found"
      else
         runHints cmd flags
 
@@ -64,7 +67,7 @@
     settings3 <- return [Classify Ignore x ("","") | x <- cmdIgnore]
     let settings = settings1 ++ settings2 ++ settings3
 
-    ideas <- fmap concat $ parallel [listM' =<< applyHint flags settings x | x <- cmdFiles]
+    ideas <- fmap concat $ parallel [listM' =<< applyHint flags settings x | x <- fromMaybe [] cmdFiles]
     let (showideas,hideideas) = partition (\i -> cmdShowAll || severity i /= Ignore) ideas
     showItem <- if cmdColor then showANSI else return show
     mapM_ (outStrLn . showItem) showideas
diff --git a/src/HSE/Bracket.hs b/src/HSE/Bracket.hs
--- a/src/HSE/Bracket.hs
+++ b/src/HSE/Bracket.hs
@@ -49,7 +49,7 @@
         | Tuple{} <- parent = False
         | If{} <- parent, isAnyApp child = False
         | App{} <- parent, i == 0, App{} <- child = False
-        | ExpTypeSig{} <- parent, i == 0, not $ isLambda child = False
+        | ExpTypeSig{} <- parent, i == 0, isApp child = False
         | Paren{} <- parent = False
         | isDotApp parent, isDotApp child, i == 1 = False
         | RecConstr{} <- parent = False
diff --git a/src/HSE/Util.hs b/src/HSE/Util.hs
--- a/src/HSE/Util.hs
+++ b/src/HSE/Util.hs
@@ -5,6 +5,7 @@
 import Control.Monad
 import Data.List
 import Data.Maybe
+import System.FilePath
 import HSE.Type
 import Language.Haskell.Exts.Annotated.Simplify(sOp, sAssoc)
 
@@ -250,7 +251,10 @@
 -- SRCLOC FUNCTIONS
 
 showSrcLoc :: SrcLoc -> String
-showSrcLoc (SrcLoc file line col) = file ++ ":" ++ show line ++ ":" ++ show col ++ ":"
+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 [] = []
 
 toSrcLoc :: SrcInfo si => si -> SrcLoc
 toSrcLoc = getPointLoc
@@ -272,8 +276,9 @@
 
 x /=~= y = not $ x =~= y
 
-elem_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> Bool
+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 (=~=)
diff --git a/src/Hint/All.hs b/src/Hint/All.hs
--- a/src/Hint/All.hs
+++ b/src/Hint/All.hs
@@ -18,6 +18,7 @@
 import Hint.Import
 import Hint.Pragma
 import Hint.Extensions
+import Hint.Duplicate
 
 
 staticHints :: [(String,Hint)]
@@ -33,6 +34,7 @@
     ,"Import"     + importHint
     ,"Pragma"     + pragmaHint
     ,"Extensions" + extensionsHint
+    ,"Duplicate"  + duplicateHint
     ]
 
 dynamicHints :: [Setting] -> Hint
diff --git a/src/Hint/Bracket.hs b/src/Hint/Bracket.hs
--- a/src/Hint/Bracket.hs
+++ b/src/Hint/Bracket.hs
@@ -21,6 +21,7 @@
 no = \(x -> y) -> z
 yes = (`foo` (bar baz)) -- @Warning (`foo` bar baz)
 
+
 -- type bracket reduction
 foo :: (Int -> Int) -> Int
 foo :: Int -> (Int -> Int) -- @Warning Int -> Int -> Int
@@ -45,6 +46,7 @@
 yes = (a b $ c d) ++ e -- a b (c d) ++ e
 no = (f . g $ a) ++ e
 no = quickCheck ((\h -> cySucc h == succ h) :: Hygiene -> Bool)
+foo = (case x of y -> z; q -> w) :: Int
 </TEST>
 -}
 
diff --git a/src/Hint/Duplicate.hs b/src/Hint/Duplicate.hs
new file mode 100644
--- /dev/null
+++ b/src/Hint/Duplicate.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
+
+{-
+Find bindings within a let, and lists of statements
+If you have n the same, error out
+
+<TEST>
+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} -- ???
+</TEST>
+-}
+
+
+module Hint.Duplicate(duplicateHint) where
+
+import Hint.Type
+import Control.Arrow
+import Data.List hiding (find)
+import qualified Data.Map as Map
+
+
+duplicateHint :: ModuHint
+duplicateHint _ modu =
+    dupes [y | Do _ y :: Exp S <- universeBi modu] ++
+    dupes [y | BDecls l y :: Binds S <- universeBi modu]
+
+
+dupes ys =
+    [rawIdea
+        (if length xs >= 5 then Error else Warning)
+        "Reduce duplication" p1
+        (unlines $ map (prettyPrint . fmap (const p1)) xs)
+        ("Combine with " ++ showSrcLoc p2) ""
+    | (p1,p2,xs) <- duplicateOrdered 3 $ map (map (toSrcLoc . ann &&& dropAnn)) ys]
+
+
+---------------------------------------------------------------------
+-- DUPLICATE FINDING
+
+-- | The position to return if we match at this point, and the map of where to go next
+--   If two runs have the same vals, always use the first pos you find
+data Dupe pos val = Dupe pos (Map.Map val (Dupe pos val))
+
+
+find :: Ord val => [val] -> Dupe pos val -> (pos, Int)
+find (v:vs) (Dupe p mp) | Just d <- Map.lookup v mp = second (+1) $ find vs d
+find _ (Dupe p mp) = (p, 0)
+
+
+add :: Ord val => pos -> [val] -> Dupe pos val -> Dupe pos val
+add pos [] d = d
+add pos (v:vs) (Dupe p mp) = Dupe p $ Map.insertWith f v (add pos vs $ Dupe pos Map.empty) mp
+    where f new old = add pos vs old
+
+
+duplicateOrdered :: Ord val => Int -> [[(SrcLoc,val)]] -> [(SrcLoc,SrcLoc,[val])]
+duplicateOrdered threshold xs = concat $ concat $ snd $ mapAccumL f (Dupe nullSrcLoc Map.empty) xs
+    where
+        f d xs = second overlaps $ mapAccumL (g pos) d $ takeWhile ((>= threshold) . length) $ tails xs
+            where pos = Map.fromList $ zip (map fst xs) [0..]
+
+        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
+                (p,i) = find vs d
+                pme = fst $ head xs
+                d2 = add pme vs d
+
+        overlaps (x@((_,_,n):_):xs) = x : overlaps (drop (length n - 1) xs)
+        overlaps (x:xs) = x : overlaps xs
+        overlaps [] = []
diff --git a/src/Hint/Extensions.hs b/src/Hint/Extensions.hs
--- a/src/Hint/Extensions.hs
+++ b/src/Hint/Extensions.hs
@@ -26,7 +26,7 @@
 {-# LANGUAGE RecordWildCards #-} \
 record field = Record{..}
 {-# LANGUAGE RecordWildCards #-} \
-record = 1 -- {-# LANGUAGE DisambiguateRecordFields #-}
+record = 1 --
 </TEST>
 -}
 
@@ -42,6 +42,7 @@
 extensionsHint :: ModuHint
 extensionsHint _ x = [rawIdea Error "Unused LANGUAGE pragma" (toSrcLoc sl)
           (prettyPrint o) (if null new then "" else prettyPrint $ LanguagePragma sl $ map (toNamed . showExt) new)
+          (warnings old new)
     | not $ used TemplateHaskell x -- if TH is on, can use all other extensions programmatically
     , o@(LanguagePragma sl exts) <- modulePragmas x
     , let old = map (classifyExtension . prettyPrint) exts
@@ -54,13 +55,13 @@
 
 minimalExtensions :: Module_ -> [Extension] -> [Extension]
 minimalExtensions x es = nub $ concatMap f es
-    where f e = if used e x then [e] else concatMap f $ implies e
+    where f e = if used e x then [e] else []
 
 
--- sometimes one extension implies others, this captures that
-implies :: Extension -> [Extension]
-implies RecordWildCards = [DisambiguateRecordFields]
-implies _ = []
+-- RecordWildCards implies DisambiguateRecordFields, but most people probably don't want it
+warnings old new | RecordWildCards `elem` old && RecordWildCards `notElem` new = "you may need to add DisambiguateRecordFields"
+warnings _ _ = ""
+
 
 used :: Extension -> Module_ -> Bool
 used RecursiveDo = hasS isMDo
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,4 @@
-{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
 {-
     Reduce the number of import declarations.
     Two import declarations can be combined if:
@@ -30,6 +30,10 @@
 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_)
+module Foo(module A, baz, module B, module C) where; import A; import D; import B(map,filter); import C \
+    -- module Foo(baz, module X) where; import A as X; import B as X(map, filter); import C as X
+module Foo(module A, baz, module B, module X) where; import A; import B; import X \
+    -- module Foo(baz, module Y) where; import A as Y; import B as Y; import X as Y
 </TEST>
 -}
 
@@ -38,17 +42,19 @@
 
 import Hint.Type
 import Util
+import Data.List
 import Data.Maybe
 
 
 importHint :: ModuHint
 importHint _ x = concatMap (wrap . snd) (groupSortFst
                  [((fromNamed $ importModule i,importPkg i),i) | i <- universeBi x, not $ importSrc i]) ++
-                 concatMap hierarchy (universeBi x)
+                 concatMap hierarchy (universeBi x) ++
+                 multiExport x
 
 
 wrap :: [ImportDecl S] -> [Idea]
-wrap o = [ rawIdea Error "Use fewer imports" (toSrcLoc $ ann $ head o) (f o) (f x)
+wrap o = [ rawIdea Error "Use fewer imports" (toSrcLoc $ ann $ head o) (f o) (f x) ""
          | Just x <- [simplify o]]
     where f = unlines . map prettyPrint
 
@@ -107,10 +113,10 @@
 -- import IO is equivalent to
 -- import System.IO, import System.IO.Error, import Control.Exception(bracket, bracket_)
 hierarchy i@ImportDecl{importModule=ModuleName _ "IO", importSpecs=Nothing,importPkg=Nothing}
-    = [rawIdea Warning "Use hierarchical imports" (toSrcLoc $ ann i) (ltrim $ prettyPrint i) $
+    = [rawIdea Warning "Use hierarchical imports" (toSrcLoc $ ann i) (ltrim $ prettyPrint i) (
           unlines $ map (ltrim . prettyPrint)
           [f "System.IO" Nothing, f "System.IO.Error" Nothing
-          ,f "Control.Exception" $ Just $ ImportSpecList an False [IVar an $ toNamed x | x <- ["bracket","bracket_"]]]]
+          ,f "Control.Exception" $ Just $ ImportSpecList an False [IVar an $ toNamed x | x <- ["bracket","bracket_"]]]) ""]
     where f a b = (desugarQual i){importModule=ModuleName an a, importSpecs=b}
 
 hierarchy _ = []
@@ -120,3 +126,24 @@
 desugarQual :: ImportDecl S -> ImportDecl S
 desugarQual x | importQualified x && isNothing (importAs x) = x{importAs=Just (importModule x)}
               | otherwise = x
+
+
+multiExport :: Module S -> [Idea]
+multiExport x =
+    [ rawIdea Warning "Use import/export shortcut" (toSrcLoc $ ann hd)
+        (unlines $ prettyPrint hd : map prettyPrint imps)
+        (unlines $ prettyPrint newhd : map prettyPrint newimps)
+        ""
+    | Module l (Just hd) _ imp _ <- [x]
+    , let asNames = mapMaybe importAs imp
+    , let expNames = [x | EModuleContents _ x <- childrenBi hd]
+    , let imps = [i | i@ImportDecl{importAs=Nothing,importQualified=False,importModule=name} <- imp
+                 ,name `notElem_` asNames, name `elem_` expNames]
+    , length imps >= 3
+    , let newname = ModuleName an $ head $ map return ("XYZ" ++ ['A'..]) \\
+                                           [x | ModuleName (_ :: S) x <- universeBi hd ++ universeBi imp]
+    , let reexport (EModuleContents _ x) = x `notElem_` map importModule imps
+          reexport x = True
+    , let newhd = descendBi (\xs -> filter reexport xs ++ [EModuleContents an newname]) hd
+    , let newimps = [i{importAs=Just newname} | i <- imps]
+    ]
diff --git a/src/Hint/Match.hs b/src/Hint/Match.hs
--- a/src/Hint/Match.hs
+++ b/src/Hint/Match.hs
@@ -77,20 +77,21 @@
 
 findIdeas :: [Setting] -> Scope -> Module S -> Decl_ -> [Idea]
 findIdeas matches s _ decl =
-  [ idea (severityS m) (hintS m) x y
-  | (parent,x) <- universeParentExp decl, not $ isParen x, let x2 = fmapAn x
-  , m <- matches, Just y <- [matchIdea s decl m parent x2]]
+  [ (idea (severityS m) (hintS m) x y){note=notes}
+  | decl <- case decl of InstDecl{} -> children decl; _ -> [decl]
+  , (parent,x) <- universeParentExp decl, not $ isParen x, let x2 = fmapAn x
+  , m <- matches, Just (y,notes) <- [matchIdea s decl m parent x2]]
 
 
-matchIdea :: Scope -> Decl_ -> Setting -> Maybe (Int, Exp_) -> Exp_ -> Maybe Exp_
-matchIdea s decl MatchExp{lhs=lhs,rhs=rhs,side=side,scope=scope} parent x = do
+matchIdea :: Scope -> Decl_ -> Setting -> Maybe (Int, Exp_) -> Exp_ -> Maybe (Exp_,String)
+matchIdea s decl MatchExp{lhs=lhs,rhs=rhs,side=side,scope=scope,notes=notes} parent x = do
     let nm = nameMatch scope s
     u <- unifyExp nm lhs x
     u <- check u
     let res = addBracket parent $ unqualify scope s u $ performEval $ subst u rhs
     guard $ checkSide side $ ("original",x) : ("result",res) : u
     guard $ checkDefine decl parent res
-    return res
+    return (res,notes)
 
 
 ---------------------------------------------------------------------
@@ -174,12 +175,16 @@
 
         f (App _ cond (sub -> y))
             | 'i':'s':typ <- fromNamed cond
-            = if typ == "Atom" then isAtom y else head (words $ show y) == typ
+            = isType typ y
         f (App _ (App _ cond (sub -> x)) (sub -> y))
             | cond ~= "notIn" = and [x `notElem` universe y | x <- list x, y <- list y]
             | cond ~= "notEq" = x /= y
         f x | x ~= "notTypeSafe" = True
         f x = error $ "Hint.Match.checkSide, unknown side condition: " ++ prettyPrint x
+
+        isType "Atom" x = isAtom x
+        isType ('L':'i':'t':typ@(_:_)) (Lit _ x) = head (words $ show x) == typ
+        isType typ x = head (words $ show x) == typ
 
         list :: Exp_ -> [Exp_]
         list (List _ xs) = xs
diff --git a/src/Hint/Monad.hs b/src/Hint/Monad.hs
--- a/src/Hint/Monad.hs
+++ b/src/Hint/Monad.hs
@@ -20,13 +20,15 @@
 yes = do x <- bar; x -- do join bar
 no = do x <- bar; x; x
 no = mdo hook <- mkTrigger pat (act >> rmHook hook) ; return hook
-yes = do x <- return y; foo x -- do let x = y; foo x
+yes = do x <- return y; foo x -- @Warning do let x = y; foo x
 yes = do x <- return $ y + z; foo x -- do let x = y + z; foo x
 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 ()
 yes = do if a then forM x y else sequence z q; return () -- if a then forM_ x y else sequence_ z q
 yes = do case a of {_ -> forM x y; x:xs -> forM x xs}; return () -- case a of _ -> forM_ x y ; x:xs -> forM_ x xs
+foldM_ f a xs = foldM f a xs >> return ()
+folder f a xs = foldM f a xs >> return () -- foldM_ f a xs
 </TEST>
 -}
 
@@ -43,19 +45,19 @@
 
 
 monadHint :: DeclHint
-monadHint _ _ = concatMap monadExp . universeBi
+monadHint _ _ d = concatMap (monadExp d) $ universeBi d
 
-monadExp :: Exp_ -> [Idea]
-monadExp x = case x of
+monadExp :: Decl_ -> Exp_ -> [Idea]
+monadExp decl x = case x of
         (view -> App2 op x1 x2) | op ~= ">>" -> f x1
         Do _ xs -> [err "Redundant return" x y | Just y <- [monadReturn xs]] ++
                    [err "Use join" x (Do an y) | Just y <- [monadJoin xs]] ++
                    [err "Redundant do" x y | [Qualifier _ y] <- [xs]] ++
-                   [err "Use let" x (Do an y) | Just y <- [monadLet xs]] ++
+                   [warn "Use let" x (Do an y) | Just y <- [monadLet xs]] ++
                    concat [f x | Qualifier _ x <- init xs]
         _ -> []
     where
-        f x = [err ("Use " ++ name) x y | Just (name,y) <- [monadCall x]]
+        f x = [err ("Use " ++ name) x y | Just (name,y) <- [monadCall x], fromNamed decl /= name]
 
 
 -- see through Paren and down if/case etc
diff --git a/src/Hint/Naming.hs b/src/Hint/Naming.hs
--- a/src/Hint/Naming.hs
+++ b/src/Hint/Naming.hs
@@ -3,7 +3,7 @@
     Suggest the use of camelCase
 
     Only permit:
-    _*[A-Za-z]*_?'?
+    _*[A-Za-z]*_*'*
 
     Apply this to things that would get exported by default only
     Also allow prop_ as it's a standard QuickCheck idiom
@@ -77,8 +77,8 @@
 suggestName :: String -> Maybe String
 suggestName x = listToMaybe [f x | not $ isSym x || good || not (any isLower x) || "prop_" `isPrefixOf` x]
     where
-        good = all isAlphaNum $ drp '_' $ drp '\'' $ reverse $ dropWhile (== '_') x
-        drp x ys = if [x] `isPrefixOf` ys then tail ys else ys
+        good = all isAlphaNum $ drp '_' $ drp '\'' $ reverse $ drp '_' x
+        drp x = dropWhile (== x)
 
         f xs = us ++ g ys
             where (us,ys) = span (== '_') xs
diff --git a/src/Hint/Pragma.hs b/src/Hint/Pragma.hs
--- a/src/Hint/Pragma.hs
+++ b/src/Hint/Pragma.hs
@@ -41,7 +41,7 @@
 
 
 pragmaIdea :: [ModulePragma S] -> [ModulePragma S] -> Idea
-pragmaIdea xs ys = rawIdea Error "Use better pragmas" (toSrcLoc $ ann $ head xs) (f xs) (f ys)
+pragmaIdea xs ys = rawIdea Error "Use better pragmas" (toSrcLoc $ ann $ head xs) (f xs) (f ys) ""
     where f = unlines . map prettyPrint
 
 
diff --git a/src/Idea.hs b/src/Idea.hs
--- a/src/Idea.hs
+++ b/src/Idea.hs
@@ -10,7 +10,7 @@
 
 
 data Idea
-    = Idea {func :: FuncName, severity :: Severity, hint :: String, loc :: SrcLoc, from :: String, to :: String}
+    = Idea {func :: FuncName, severity :: Severity, hint :: String, loc :: SrcLoc, from :: String, to :: String, note :: String}
     | ParseError {severity :: Severity, hint :: String, loc :: SrcLoc, msg :: String, from :: String}
       deriving (Eq,Ord)
 
@@ -29,15 +29,20 @@
 
 showEx :: (String -> String) -> Idea -> String
 showEx tt Idea{..} = unlines $
-    [showSrcLoc loc ++ " " ++ show severity ++ ": " ++ hint] ++ f "Found" from ++ f "Why not" to
-    where f msg x = (msg ++ ":") : map ("  "++) (lines $ tt x)
+    [showSrcLoc loc ++ ": " ++ show severity ++ ": " ++ hint] ++
+    f "Found" from ++ f "Why not" to ++
+    ["Note: " ++ note | note /= ""]
+    where
+        f msg x | null xs = [msg ++ " remove it."]
+                | otherwise = (msg ++ ":") : map ("  "++) xs
+            where xs = lines $ tt x
 
 showEx tt ParseError{..} = unlines $
-    [showSrcLoc loc ++ " Parse error","Error message:","  " ++ msg,"Code:"] ++ map ("  "++) (lines $ tt from)
+    [showSrcLoc loc ++ ": Parse error","Error message:","  " ++ msg,"Code:"] ++ map ("  "++) (lines $ tt from)
 
 
 rawIdea = Idea ("","")
-idea severity hint from to = rawIdea severity hint (toSrcLoc $ ann from) (f from) (f to)
+idea severity hint from to = rawIdea severity hint (toSrcLoc $ ann from) (f from) (f to) ""
     where f = ltrim . prettyPrint
 warn = idea Warning
 err = idea Error
diff --git a/src/Report.hs b/src/Report.hs
--- a/src/Report.hs
+++ b/src/Report.hs
@@ -51,18 +51,19 @@
 writeIdea :: String -> Idea -> [String]
 writeIdea cls Idea{..} =
     ["<div class=" ++ show cls ++ ">"
-    ,escapeHTML (showSrcLoc loc ++ " " ++ show severity ++ ": " ++ hint) ++ "<br/>"
+    ,escapeHTML (showSrcLoc loc ++ ": " ++ show severity ++ ": " ++ hint) ++ "<br/>"
     ,"Found<br/>"
     ,code from
-    ,"Why not<br/>"
+    ,"Why not" ++ (if to == "" then " remove it." else "") ++ "<br/>"
     ,code to
+    ,if note /= "" then "<span class='note'>Note: " ++ note ++ "</span>" else ""
     ,"</div>"
     ,""]
 
 
 writeIdea cls ParseError{..} =
     ["<div class=" ++ show cls ++ ">"
-    ,escapeHTML (showSrcLoc loc ++ " " ++ show severity ++ ": " ++ hint) ++ "<br/>"
+    ,escapeHTML (showSrcLoc loc ++ ": " ++ show severity ++ ": " ++ hint) ++ "<br/>"
     ,"Error message<br/>"
     ,"<pre>" ++ escapeHTML msg ++ "</pre>"
     ,"Code<br/>"
diff --git a/src/Settings.hs b/src/Settings.hs
--- a/src/Settings.hs
+++ b/src/Settings.hs
@@ -50,7 +50,7 @@
 
 data Setting
     = Classify {severityS :: Severity, hintS :: String, funcS :: FuncName}
-    | MatchExp {severityS :: Severity, hintS :: String, scope :: Scope, lhs :: Exp_, rhs :: Exp_, side :: Maybe Exp_}
+    | MatchExp {severityS :: Severity, hintS :: String, scope :: Scope, lhs :: Exp_, rhs :: Exp_, side :: Maybe Exp_, notes :: String}
     | Builtin String -- use a builtin hint set
     | Infix Fixity
       deriving Show
@@ -86,12 +86,14 @@
 readSetting :: Scope -> Decl_ -> [Setting]
 readSetting s (FunBind _ [Match _ (Ident _ (getSeverity -> Just severity)) pats (UnGuardedRhs _ bod) bind])
     | InfixApp _ lhs op rhs <- bod, opExp op ~= "==>" =
-        [MatchExp severity (if null names then defaultHintName else head names) s (fromParen lhs) (fromParen rhs) (readSide $ childrenBi bind)]
+        let (a,b) = readSide $ childrenBi bind in
+        [MatchExp severity (if null names then defaultHintName else head names) s (fromParen lhs) (fromParen rhs) a b]
     | otherwise = [Classify severity n func | n <- names2, func <- readFuncs bod]
     where
         names = filter notNull $ getNames pats bod
         names2 = ["" | null names] ++ names
 
+readSetting s x | "test" `isPrefixOf` map toLower (fromNamed x) = []
 readSetting s x@AnnPragma{} | Just y <- readPragma x = [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
@@ -116,10 +118,11 @@
 readPragma _ = Nothing
 
 
-readSide :: [Decl_] -> Maybe Exp_
-readSide [] = Nothing
-readSide [PatBind _ PWildCard{} Nothing (UnGuardedRhs _ bod) Nothing] = Just bod
-readSide (x:_) = errorOn x "bad side condition"
+readSide :: [Decl_] -> (Maybe Exp_, String)
+readSide = foldl f (Nothing,"")
+    where f (Nothing,warn) (PatBind _ PWildCard{} Nothing (UnGuardedRhs _ side) Nothing) = (Just side, warn)
+          f (side,"") (PatBind _ (fromNamed -> "note") Nothing (UnGuardedRhs _ (Lit _ (String _ warn _))) Nothing) = (side,warn)
+          f _ x = errorOn x "bad side condition"
 
 
 -- Note: Foo may be ("","Foo") or ("Foo",""), return both
@@ -147,8 +150,8 @@
 
 errorOn :: (Annotated ast, Pretty (ast S)) => ast S -> String -> b
 errorOn val msg = exitMessage $
-    showSrcLoc (getPointLoc $ ann val)  ++
-    " Error while reading hint file, " ++ msg ++ "\n" ++
+    showSrcLoc (getPointLoc $ ann val) ++
+    ": Error while reading hint file, " ++ msg ++ "\n" ++
     prettyPrint val
 
 
diff --git a/src/Test.hs b/src/Test.hs
--- a/src/Test.hs
+++ b/src/Test.hs
@@ -116,7 +116,7 @@
             ,"_eval_ = id"] ++
             ["{-# LINE " ++ show (startLine $ ann rhs) ++ " " ++ show (fileName $ ann rhs) ++ " #-}\n" ++
              prettyPrint (PatBind an (toNamed $ "test" ++ show i) Nothing bod Nothing)
-            | (i, MatchExp _ _ _ lhs rhs side) <- zip [1..] matches, "notTypeSafe" `notElem` vars side
+            | (i, MatchExp _ _ _ lhs rhs side _) <- zip [1..] matches, "notTypeSafe" `notElem` vars side
             , let vs = map toNamed $ nub $ 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]
@@ -200,19 +200,19 @@
         else if has "lhs" then return ["tests/" ++ pre <.> "lhs"]
         else error "checkInputOutput, couldn't find or figure out flags"
 
-    got <- captureOutput $
+    got <- fmap (fmap lines) $ captureOutput $
         handle (\(e::SomeException) -> print $ e) $
         handle (\(e::ExitCode) -> return ()) $
         main flags
-    want <- reader "output"
+    want <- fmap lines $ reader "output"
 
+    let eq w g = w == g || ("*" `isSuffixOf` w && init w `isPrefixOf` g)
     case got of
         Nothing -> putStrLn "Warning: failed to capture output (GHC too old?)" >> return pass
-        Just got | got == want -> return pass
+        Just got | length got == length want && and (zipWith eq want got) -> return pass
                  | otherwise -> do
-            (got,want) <- return (lines got, lines want)
             let trail = replicate (max (length got) (length want)) "<EOF>"
-            let (i,g,w):_ = [(i,g,w) | (i,g,w) <- zip3 [1..] (got++trail) (want++trail), g /= w]
+            let (i,g,w):_ = [(i,g,w) | (i,g,w) <- zip3 [1..] (got++trail) (want++trail), not $ eq g w]
             putStrLn $ unlines
                 ["TEST FAILURE IN tests/" ++ pre
                 ,"DIFFER ON LINE: " ++ show i
