diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2016-2017 Josh Clayton
+Copyright (c) 2016-2018 Josh Clayton
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,3 @@
 import Distribution.Simple
+
 main = defaultMain
diff --git a/app/App.hs b/app/App.hs
--- a/app/App.hs
+++ b/app/App.hs
@@ -1,77 +1,50 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE FlexibleContexts #-}
 
 module App
-    ( Options(..)
-    , runProgram
+    ( runProgram
     ) where
 
-import           Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)
-import           Control.Monad.Reader (ReaderT, MonadReader, MonadIO, runReaderT, asks, liftIO)
+import Control.Monad.Except (runExceptT, throwError)
+import Control.Monad.Reader (asks, liftIO, runReaderT)
 import qualified Data.Bifunctor as BF
 import qualified Data.Bool as B
 import qualified Data.Maybe as M
-import           Unused.Aliases (termsAndAliases)
-import           Unused.CLI (SearchRunner(..), loadGitContext, renderHeader, executeSearch, withRuntime)
+import Types
+import Unused.Aliases (termsAndAliases)
+import Unused.CLI
+       (SearchRunner(..), executeSearch, loadGitContext, renderHeader,
+        withRuntime)
 import qualified Unused.CLI.Views as V
-import           Unused.Cache (FingerprintOutcome(..), cached)
-import           Unused.Grouping (CurrentGrouping(..), groupedResponses)
-import           Unused.Parser (parseResults)
-import           Unused.ResponseFilter (withOneOccurrence, withLikelihoods, ignoringPaths)
-import           Unused.ResultsClassifier (ParseConfigError, LanguageConfiguration(..), loadAllConfigurations)
-import           Unused.TagsSource (TagSearchOutcome, loadTagsFromFile, loadTagsFromPipe)
-import           Unused.TermSearch (SearchResults(..), SearchBackend(..), SearchTerm, fromResults)
-import           Unused.Types (TermMatchSet, RemovalLikelihood(..))
-
-type AppConfig = MonadReader Options
-
-data AppError
-    = TagError TagSearchOutcome
-    | InvalidConfigError [ParseConfigError]
-    | CacheError FingerprintOutcome
-
-newtype App a = App {
-    runApp :: ReaderT Options (ExceptT AppError IO) a
-} deriving (Monad, Functor, Applicative, AppConfig, MonadError AppError, MonadIO)
-
-data Options = Options
-    { oSearchRunner :: SearchRunner
-    , oSingleOccurrenceMatches :: Bool
-    , oLikelihoods :: [RemovalLikelihood]
-    , oAllLikelihoods :: Bool
-    , oIgnoredPaths :: [String]
-    , oGrouping :: CurrentGrouping
-    , oWithoutCache :: Bool
-    , oFromStdIn :: Bool
-    , oCommitCount :: Maybe Int
-    , oSearchBackend :: SearchBackend
-    }
+import Unused.Cache (cached)
+import Unused.Grouping (CurrentGrouping(..), groupedResponses)
+import Unused.Parser (parseResults)
+import Unused.ResponseFilter
+       (ignoringPaths, withLikelihoods, withOneOccurrence)
+import Unused.ResultsClassifier
+       (LanguageConfiguration(..), loadAllConfigurations)
+import Unused.TagsSource (loadTagsFromFile, loadTagsFromPipe)
+import Unused.TermSearch
+       (SearchBackend(..), SearchResults(..), SearchTerm, fromResults)
+import Unused.Types (RemovalLikelihood(..), TermMatchSet)
 
 runProgram :: Options -> IO ()
-runProgram options = withRuntime $
-    either renderError return
-        =<< runExceptT (runReaderT (runApp run) options)
+runProgram options =
+    withRuntime $ either renderError return =<< runExceptT (runReaderT (runApp run) options)
 
 run :: App ()
 run = do
     terms <- termsWithAlternatesFromConfig
-
     liftIO $ renderHeader terms
     backend <- searchBackend
     results <- withCache . flip (executeSearch backend) terms =<< searchRunner
-
     printResults =<< retrieveGitContext =<< fmap (`parseResults` results) loadAllConfigs
 
 searchBackend :: AppConfig m => m SearchBackend
 searchBackend = asks oSearchBackend
 
 termsWithAlternatesFromConfig :: App [SearchTerm]
-termsWithAlternatesFromConfig = do
-    aliases <- concatMap lcTermAliases <$> loadAllConfigs
-    terms <- calculateTagInput
-
-    return $ termsAndAliases aliases terms
+termsWithAlternatesFromConfig =
+    termsAndAliases <$> (concatMap lcTermAliases <$> loadAllConfigs) <*> calculateTagInput
 
 renderError :: AppError -> IO ()
 renderError (TagError e) = V.missingTagsFileError e
@@ -79,9 +52,7 @@
 renderError (CacheError e) = V.fingerprintError e
 
 retrieveGitContext :: TermMatchSet -> App TermMatchSet
-retrieveGitContext tms =
-    maybe (return tms) (liftIO . flip loadGitContext tms)
-        =<< numberOfCommits
+retrieveGitContext tms = maybe (return tms) (liftIO . flip loadGitContext tms) =<< numberOfCommits
 
 printResults :: TermMatchSet -> App ()
 printResults tms = do
@@ -92,42 +63,31 @@
 
 loadAllConfigs :: App [LanguageConfiguration]
 loadAllConfigs =
-    either throwError return
-        =<< BF.first InvalidConfigError <$> liftIO loadAllConfigurations
+    either throwError return =<< BF.first InvalidConfigError <$> liftIO loadAllConfigurations
 
 calculateTagInput :: App [String]
 calculateTagInput =
-    either throwError return
-        =<< liftIO .
-            fmap (BF.first TagError) .
-            B.bool loadTagsFromFile loadTagsFromPipe =<< readFromStdIn
+    either throwError return =<<
+    liftIO . fmap (BF.first TagError) . B.bool loadTagsFromFile loadTagsFromPipe =<< readFromStdIn
 
 withCache :: IO SearchResults -> App SearchResults
-withCache f =
-    B.bool (liftIO f) (withCache' f) =<< runWithCache
+withCache f = B.bool (liftIO f) (withCache' f) =<< runWithCache
   where
     withCache' :: IO SearchResults -> App SearchResults
     withCache' r =
         either (throwError . CacheError) (return . SearchResults) =<<
-            liftIO (cached "term-matches" $ fmap fromResults r)
-
+        liftIO (cached "term-matches" $ fmap fromResults r)
 
 optionFilters :: AppConfig m => TermMatchSet -> m TermMatchSet
 optionFilters tms = foldl (>>=) (pure tms) matchSetFilters
   where
-    matchSetFilters =
-        [ singleOccurrenceFilter
-        , likelihoodsFilter
-        , ignoredPathsFilter
-        ]
+    matchSetFilters = [singleOccurrenceFilter, likelihoodsFilter, ignoredPathsFilter]
 
 singleOccurrenceFilter :: AppConfig m => TermMatchSet -> m TermMatchSet
-singleOccurrenceFilter tms =
-    B.bool tms (withOneOccurrence tms) <$> asks oSingleOccurrenceMatches
+singleOccurrenceFilter tms = B.bool tms (withOneOccurrence tms) <$> asks oSingleOccurrenceMatches
 
 likelihoodsFilter :: AppConfig m => TermMatchSet -> m TermMatchSet
-likelihoodsFilter tms =
-     asks $ withLikelihoods . likelihoods <*> pure tms
+likelihoodsFilter tms = asks $ withLikelihoods . likelihoods <*> pure tms
   where
     likelihoods options
         | oAllLikelihoods options = [High, Medium, Low]
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,58 +1,51 @@
 module Main where
 
-import           App (runProgram, Options(Options))
-import           Common
+import App (runProgram)
+import Common
 import qualified Data.Maybe as M
-import           Options.Applicative
-import           Unused.CLI (SearchRunner(..))
-import           Unused.Grouping (CurrentGrouping(..))
-import           Unused.TermSearch (SearchBackend(..))
-import           Unused.Types (RemovalLikelihood(..))
-import           Unused.Util (stringToInt)
+import Options.Applicative
+import Types (Options(Options))
+import Unused.CLI (SearchRunner(..))
+import Unused.Grouping (CurrentGrouping(..))
+import Unused.TermSearch (SearchBackend(..))
+import Unused.Types (RemovalLikelihood(..))
+import Unused.Util (stringToInt)
 
 main :: IO ()
 main = runProgram =<< parseCLI
 
 parseCLI :: IO Options
-parseCLI =
-    execParser (withInfo parseOptions pHeader pDescription pFooter)
+parseCLI = execParser (withInfo parseOptions pHeader pDescription pFooter)
   where
-    pHeader      = "Unused: Analyze potentially unused code"
-    pDescription = "Unused allows a developer to leverage an existing tags file \
+    pHeader = "Unused: Analyze potentially unused code"
+    pDescription =
+        "Unused allows a developer to leverage an existing tags file \
                    \(located at .git/tags, tags, or tmp/tags) to identify tokens \
                    \in a codebase that are unused."
-    pFooter      = "CLI USAGE: $ unused"
+    pFooter = "CLI USAGE: $ unused"
 
 withInfo :: Parser a -> String -> String -> String -> ParserInfo a
-withInfo opts h d f =
-    info (helper <*> opts) $ header h <> progDesc d <> footer f
+withInfo opts h d f = info (helper <*> opts) $ header h <> progDesc d <> footer f
 
 parseOptions :: Parser Options
 parseOptions =
-    Options
-    <$> parseSearchRunner
-    <*> parseDisplaySingleOccurrenceMatches
-    <*> parseLikelihoods
-    <*> parseAllLikelihoods
-    <*> parseIgnorePaths
-    <*> parseGroupings
-    <*> parseWithoutCache
-    <*> parseFromStdIn
-    <*> parseCommitCount
-    <*> parseSearchBackend
+    Options <$> parseSearchRunner <*> parseDisplaySingleOccurrenceMatches <*> parseLikelihoods <*>
+    parseAllLikelihoods <*>
+    parseIgnorePaths <*>
+    parseGroupings <*>
+    parseWithoutCache <*>
+    parseFromStdIn <*>
+    parseCommitCount <*>
+    parseSearchBackend
 
 parseSearchRunner :: Parser SearchRunner
 parseSearchRunner =
     flag SearchWithProgress SearchWithoutProgress $
-        short 'P'
-        <> long "no-progress"
-        <> help "Don't display progress during analysis"
+    short 'P' <> long "no-progress" <> help "Don't display progress during analysis"
 
 parseDisplaySingleOccurrenceMatches :: Parser Bool
-parseDisplaySingleOccurrenceMatches = switch $
-    short 's'
-    <> long "single-occurrence"
-    <> help "Display only single occurrences"
+parseDisplaySingleOccurrenceMatches =
+    switch $ short 's' <> long "single-occurrence" <> help "Display only single occurrences"
 
 parseLikelihoods :: Parser [RemovalLikelihood]
 parseLikelihoods = many (parseLikelihood <$> parseLikelihoodOption)
@@ -64,26 +57,22 @@
 parseLikelihood _ = Unknown
 
 parseLikelihoodOption :: Parser String
-parseLikelihoodOption = strOption $
-    short 'l'
-    <> long "likelihood"
-    <> help "[Allows multiple] [Allowed: high, medium, low] Display results based on likelihood"
+parseLikelihoodOption =
+    strOption $
+    short 'l' <> long "likelihood" <>
+    help "[Allows multiple] [Allowed: high, medium, low] Display results based on likelihood"
 
 parseAllLikelihoods :: Parser Bool
-parseAllLikelihoods = switch $
-    short 'a'
-    <> long "all-likelihoods"
-    <> help "Display all likelihoods"
+parseAllLikelihoods = switch $ short 'a' <> long "all-likelihoods" <> help "Display all likelihoods"
 
 parseIgnorePaths :: Parser [String]
-parseIgnorePaths = many $ strOption $
-    long "ignore"
-    <> metavar "PATH"
-    <> help "[Allows multiple] Ignore paths that contain PATH"
+parseIgnorePaths =
+    many $
+    strOption $
+    long "ignore" <> metavar "PATH" <> help "[Allows multiple] Ignore paths that contain PATH"
 
 parseGroupings :: Parser CurrentGrouping
-parseGroupings =
-    M.fromMaybe GroupByDirectory <$> maybeGroup
+parseGroupings = M.fromMaybe GroupByDirectory <$> maybeGroup
   where
     maybeGroup = optional $ parseGrouping <$> parseGroupingOption
 
@@ -95,38 +84,30 @@
 parseGrouping _ = NoGroup
 
 parseGroupingOption :: Parser String
-parseGroupingOption = strOption $
-    short 'g'
-    <> long "group-by"
-    <> help "[Allowed: directory, term, file, none] Group results"
+parseGroupingOption =
+    strOption $
+    short 'g' <> long "group-by" <> help "[Allowed: directory, term, file, none] Group results"
 
 parseWithoutCache :: Parser Bool
-parseWithoutCache = switch $
-    short 'C'
-    <> long "no-cache"
-    <> help "Ignore cache when performing calculations"
+parseWithoutCache =
+    switch $ short 'C' <> long "no-cache" <> help "Ignore cache when performing calculations"
 
 parseFromStdIn :: Parser Bool
-parseFromStdIn = switch $
-    long "stdin"
-    <> help "Read tags from STDIN"
+parseFromStdIn = switch $ long "stdin" <> help "Read tags from STDIN"
 
 parseCommitCount :: Parser (Maybe Int)
-parseCommitCount =
-    (stringToInt =<<) <$> commitParser
+parseCommitCount = (stringToInt =<<) <$> commitParser
   where
-    commitParser = optional $ strOption $
-        long "commits"
-        <> help "Number of recent commit SHAs to display per token"
+    commitParser =
+        optional $
+        strOption $ long "commits" <> help "Number of recent commit SHAs to display per token"
 
 parseSearchBackend :: Parser SearchBackend
 parseSearchBackend = M.fromMaybe Ag <$> maybeBackend
   where
     maybeBackend = optional $ parseBackend <$> parseBackendOption
     parseBackendOption =
-        strOption $
-        long "search"
-        <> help "[Allowed: ag, rg] Select searching backend"
+        strOption $ long "search" <> help "[Allowed: ag, rg] Select searching backend"
 
 parseBackend :: String -> SearchBackend
 parseBackend "ag" = Ag
diff --git a/app/Types.hs b/app/Types.hs
new file mode 100644
--- /dev/null
+++ b/app/Types.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ConstraintKinds #-}
+
+module Types
+    ( Options(..)
+    , AppConfig
+    , AppError(..)
+    , App(..)
+    ) where
+
+import Control.Monad.Except (ExceptT, MonadError)
+import Control.Monad.Reader (MonadIO, MonadReader, ReaderT)
+import Unused.CLI (SearchRunner)
+import Unused.Cache (FingerprintOutcome)
+import Unused.Grouping (CurrentGrouping)
+import Unused.ResultsClassifier (ParseConfigError)
+import Unused.TagsSource (TagSearchOutcome)
+import Unused.TermSearch (SearchBackend)
+import Unused.Types (RemovalLikelihood)
+
+data Options = Options
+    { oSearchRunner :: SearchRunner
+    , oSingleOccurrenceMatches :: Bool
+    , oLikelihoods :: [RemovalLikelihood]
+    , oAllLikelihoods :: Bool
+    , oIgnoredPaths :: [String]
+    , oGrouping :: CurrentGrouping
+    , oWithoutCache :: Bool
+    , oFromStdIn :: Bool
+    , oCommitCount :: Maybe Int
+    , oSearchBackend :: SearchBackend
+    }
+
+type AppConfig = MonadReader Options
+
+data AppError
+    = TagError TagSearchOutcome
+    | InvalidConfigError [ParseConfigError]
+    | CacheError FingerprintOutcome
+
+newtype App a = App
+    { runApp :: ReaderT Options (ExceptT AppError IO) a
+    } deriving (Monad, Functor, Applicative, AppConfig, MonadError AppError, MonadIO)
diff --git a/src/Common.hs b/src/Common.hs
--- a/src/Common.hs
+++ b/src/Common.hs
@@ -3,7 +3,6 @@
 module Common
     ( (<>)
     ) where
-
 #if MIN_VERSION_base(4, 8, 0)
 import Data.Monoid ((<>))
 #endif
diff --git a/src/Unused/Aliases.hs b/src/Unused/Aliases.hs
--- a/src/Unused/Aliases.hs
+++ b/src/Unused/Aliases.hs
@@ -4,11 +4,11 @@
     ) where
 
 import qualified Data.List as L
-import           Data.Text (Text)
+import Data.Text (Text)
 import qualified Data.Text as T
-import           Unused.ResultsClassifier.Types
-import           Unused.Types (SearchTerm(..), TermMatch, tmTerm)
-import           Unused.Util (groupBy)
+import Unused.ResultsClassifier.Types
+import Unused.Types (SearchTerm(..), TermMatch, tmTerm)
+import Unused.Util (groupBy)
 
 groupedTermsAndAliases :: [TermMatch] -> [[TermMatch]]
 groupedTermsAndAliases = map snd . groupBy tmTerm
@@ -18,17 +18,17 @@
 termsAndAliases as = L.nub . concatMap ((as >>=) . generateSearchTerms . T.pack)
 
 generateSearchTerms :: Text -> TermAlias -> [SearchTerm]
-generateSearchTerms term TermAlias{taFrom = from, taTransform = transform} =
+generateSearchTerms term TermAlias {taFrom = from, taTransform = transform} =
     toTermWithAlias $ parsePatternForMatch (T.pack from) term
   where
-    toTermWithAlias (Right (Just match)) = [OriginalTerm unpackedTerm, AliasTerm unpackedTerm (aliasedResult match)]
+    toTermWithAlias (Right (Just match)) =
+        [OriginalTerm unpackedTerm, AliasTerm unpackedTerm (aliasedResult match)]
     toTermWithAlias _ = [OriginalTerm unpackedTerm]
     unpackedTerm = T.unpack term
     aliasedResult = T.unpack . transform
 
 parsePatternForMatch :: Text -> Text -> Either Text (Maybe Text)
-parsePatternForMatch aliasPattern term =
-    findMatch $ T.splitOn wildcard aliasPattern
+parsePatternForMatch aliasPattern term = findMatch $ T.splitOn wildcard aliasPattern
   where
     findMatch [prefix, suffix] = Right $ T.stripSuffix suffix =<< T.stripPrefix prefix term
     findMatch _ = Left $ T.pack $ "There was a problem with the pattern: " ++ show aliasPattern
diff --git a/src/Unused/CLI/GitContext.hs b/src/Unused/CLI/GitContext.hs
--- a/src/Unused/CLI/GitContext.hs
+++ b/src/Unused/CLI/GitContext.hs
@@ -3,11 +3,12 @@
     ) where
 
 import qualified Data.Map.Strict as Map
-import           Unused.CLI.ProgressIndicator (createProgressBar, progressWithIndicator)
+import Unused.CLI.ProgressIndicator
+       (createProgressBar, progressWithIndicator)
 import qualified Unused.CLI.Util as U
 import qualified Unused.CLI.Views as V
-import           Unused.GitContext (gitContextForResults)
-import           Unused.Types (TermMatchSet)
+import Unused.GitContext (gitContextForResults)
+import Unused.Types (TermMatchSet)
 
 loadGitContext :: Int -> TermMatchSet -> IO TermMatchSet
 loadGitContext i tms = do
diff --git a/src/Unused/CLI/ProgressIndicator/Internal.hs b/src/Unused/CLI/ProgressIndicator/Internal.hs
--- a/src/Unused/CLI/ProgressIndicator/Internal.hs
+++ b/src/Unused/CLI/ProgressIndicator/Internal.hs
@@ -8,37 +8,38 @@
 import qualified Control.Concurrent as CC
 import qualified Control.Monad as M
 import qualified System.ProgressBar as PB
-import           Unused.CLI.ProgressIndicator.Types (ProgressIndicator(..))
-import           Unused.CLI.Util
+import Unused.CLI.ProgressIndicator.Types (ProgressIndicator(..))
+import Unused.CLI.Util
 
 start :: ProgressIndicator -> Int -> IO (CC.ThreadId, ProgressIndicator)
-start s@Spinner{} _ = do
+start s@Spinner {} _ = do
     tid <- CC.forkIO $ runSpinner 0 s
-    return (tid, s { sThreadId = Just tid })
-start ProgressBar{} i = do
+    return (tid, s {sThreadId = Just tid})
+start ProgressBar {} i = do
     (ref, tid) <- buildProgressBar $ toInteger i
     return (tid, ProgressBar (Just ref) (Just tid))
 
 stop :: ProgressIndicator -> IO ()
-stop ProgressBar{ pbThreadId = Just tid } = CC.killThread tid
-stop Spinner{ sThreadId = Just tid } = CC.killThread tid
+stop ProgressBar {pbThreadId = Just tid} = CC.killThread tid
+stop Spinner {sThreadId = Just tid} = CC.killThread tid
 stop _ = return ()
 
 increment :: ProgressIndicator -> IO ()
-increment ProgressBar{ pbProgressRef = Just ref } = PB.incProgress ref 1
+increment ProgressBar {pbProgressRef = Just ref} = PB.incProgress ref 1
 increment _ = return ()
 
 printPrefix :: ProgressIndicator -> IO ()
-printPrefix ProgressBar{} = putStr "\n\n"
-printPrefix Spinner{} = putStr " "
+printPrefix ProgressBar {} = putStr "\n\n"
+printPrefix Spinner {} = putStr " "
 
 runSpinner :: Int -> ProgressIndicator -> IO ()
-runSpinner i s@Spinner{ sDelay = delay, sSnapshots = snapshots, sColors = colors, sLength = length' } = M.forever $ do
-    setSGR [SetColor Foreground Dull currentColor]
-    putStr currentSnapshot
-    cursorBackward 1
-    CC.threadDelay delay
-    runSpinner (i + 1) s
+runSpinner i s@Spinner {sDelay = delay, sSnapshots = snapshots, sColors = colors, sLength = length'} =
+    M.forever $ do
+        setSGR [SetColor Foreground Dull currentColor]
+        putStr currentSnapshot
+        cursorBackward 1
+        CC.threadDelay delay
+        runSpinner (i + 1) s
   where
     currentSnapshot = snapshots !! (i `mod` snapshotLength)
     currentColor = colors !! (i `div` snapshotLength)
@@ -46,8 +47,7 @@
 runSpinner _ _ = return ()
 
 buildProgressBar :: Integer -> IO (PB.ProgressRef, CC.ThreadId)
-buildProgressBar =
-    PB.startProgress (PB.msg message) PB.percentage progressBarWidth
+buildProgressBar = PB.startProgress (PB.msg message) PB.percentage progressBarWidth
   where
     message = "Working"
     progressBarWidth = 60
diff --git a/src/Unused/CLI/ProgressIndicator/Types.hs b/src/Unused/CLI/ProgressIndicator/Types.hs
--- a/src/Unused/CLI/ProgressIndicator/Types.hs
+++ b/src/Unused/CLI/ProgressIndicator/Types.hs
@@ -7,14 +7,10 @@
 import qualified System.ProgressBar as PB
 
 data ProgressIndicator
-    = Spinner
-        { sSnapshots :: [String]
-        , sLength :: Int
-        , sDelay :: Int
-        , sColors :: [ANSI.Color]
-        , sThreadId :: Maybe CC.ThreadId
-        }
-    | ProgressBar
-        { pbProgressRef :: Maybe PB.ProgressRef
-        , pbThreadId :: Maybe CC.ThreadId
-        }
+    = Spinner { sSnapshots :: [String]
+              , sLength :: Int
+              , sDelay :: Int
+              , sColors :: [ANSI.Color]
+              , sThreadId :: Maybe CC.ThreadId }
+    | ProgressBar { pbProgressRef :: Maybe PB.ProgressRef
+                  , pbThreadId :: Maybe CC.ThreadId }
diff --git a/src/Unused/CLI/Search.hs b/src/Unused/CLI/Search.hs
--- a/src/Unused/CLI/Search.hs
+++ b/src/Unused/CLI/Search.hs
@@ -9,7 +9,9 @@
 import qualified Unused.CLI.Views as V
 import qualified Unused.TermSearch as TS
 
-data SearchRunner = SearchWithProgress | SearchWithoutProgress
+data SearchRunner
+    = SearchWithProgress
+    | SearchWithoutProgress
 
 renderHeader :: [a] -> IO ()
 renderHeader terms = do
@@ -22,5 +24,5 @@
     runSearch backend runner terms <* U.resetScreen
 
 runSearch :: TS.SearchBackend -> SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
-runSearch b SearchWithProgress    = I.progressWithIndicator (TS.search b) I.createProgressBar
+runSearch b SearchWithProgress = I.progressWithIndicator (TS.search b) I.createProgressBar
 runSearch b SearchWithoutProgress = I.progressWithIndicator (TS.search b) I.createSpinner
diff --git a/src/Unused/CLI/Views/InvalidConfigError.hs b/src/Unused/CLI/Views/InvalidConfigError.hs
--- a/src/Unused/CLI/Views/InvalidConfigError.hs
+++ b/src/Unused/CLI/Views/InvalidConfigError.hs
@@ -2,20 +2,18 @@
     ( invalidConfigError
     ) where
 
-import           Unused.CLI.Util
+import Unused.CLI.Util
 import qualified Unused.CLI.Views.Error as V
-import           Unused.ResultsClassifier (ParseConfigError(..))
+import Unused.ResultsClassifier (ParseConfigError(..))
 
 invalidConfigError :: [ParseConfigError] -> IO ()
 invalidConfigError es = do
     V.errorHeader "There was a problem with the following config file(s):"
-
     mapM_ configError es
-
     setSGR [Reset]
 
 configError :: ParseConfigError -> IO ()
-configError ParseConfigError{ pcePath = path, pceParseError = msg} = do
+configError ParseConfigError {pcePath = path, pceParseError = msg} = do
     setSGR [SetConsoleIntensity BoldIntensity]
     putStrLn path
     setSGR [Reset]
diff --git a/src/Unused/CLI/Views/NoResultsFound.hs b/src/Unused/CLI/Views/NoResultsFound.hs
--- a/src/Unused/CLI/Views/NoResultsFound.hs
+++ b/src/Unused/CLI/Views/NoResultsFound.hs
@@ -6,7 +6,7 @@
 
 noResultsFound :: IO ()
 noResultsFound = do
-    setSGR   [SetColor Foreground Dull Green]
-    setSGR   [SetConsoleIntensity BoldIntensity]
+    setSGR [SetColor Foreground Dull Green]
+    setSGR [SetConsoleIntensity BoldIntensity]
     putStrLn "Unused found no results"
-    setSGR   [Reset]
+    setSGR [Reset]
diff --git a/src/Unused/CLI/Views/SearchResult.hs b/src/Unused/CLI/Views/SearchResult.hs
--- a/src/Unused/CLI/Views/SearchResult.hs
+++ b/src/Unused/CLI/Views/SearchResult.hs
@@ -3,16 +3,16 @@
     , searchResults
     ) where
 
-import           Control.Arrow ((&&&))
+import Control.Arrow ((&&&))
 import qualified Data.Map.Strict as Map
-import           Unused.CLI.Util
+import Unused.CLI.Util
 import qualified Unused.CLI.Views.NoResultsFound as V
-import           Unused.CLI.Views.SearchResult.ColumnFormatter
+import Unused.CLI.Views.SearchResult.ColumnFormatter
 import qualified Unused.CLI.Views.SearchResult.ListResult as V
 import qualified Unused.CLI.Views.SearchResult.TableResult as V
-import           Unused.CLI.Views.SearchResult.Types
-import           Unused.Grouping (Grouping(..), GroupedTerms)
-import           Unused.Types (TermMatchSet, TermResults(..), TermMatch)
+import Unused.CLI.Views.SearchResult.Types
+import Unused.Grouping (GroupedTerms, Grouping(..))
+import Unused.Types (TermMatch, TermMatchSet, TermResults(..))
 
 searchResults :: ResultsFormat -> [GroupedTerms] -> IO ()
 searchResults format terms = do
@@ -28,8 +28,7 @@
 printFormattedTerms ts = mapM_ printGroupingSection ts
 
 listFromMatchSet :: TermMatchSet -> [(String, TermResults)]
-listFromMatchSet =
-  Map.toList
+listFromMatchSet = Map.toList
 
 printGroupingSection :: GroupedTerms -> ResultsPrinter ()
 printGroupingSection (g, tms) = do
@@ -46,8 +45,7 @@
     setSGR [Reset]
 
 printTermResults :: (String, TermResults) -> ResultsPrinter ()
-printTermResults =
-    uncurry printMatches . (id &&& trMatches) . snd
+printTermResults = uncurry printMatches . (id &&& trMatches) . snd
 
 printMatches :: TermResults -> [TermMatch] -> ResultsPrinter ()
 printMatches r ms = do
diff --git a/src/Unused/Cache/DirectoryFingerprint.hs b/src/Unused/Cache/DirectoryFingerprint.hs
--- a/src/Unused/Cache/DirectoryFingerprint.hs
+++ b/src/Unused/Cache/DirectoryFingerprint.hs
@@ -3,27 +3,30 @@
     , sha
     ) where
 
-import           Control.Monad.Reader (ReaderT, runReaderT, asks, liftIO)
+import Control.Monad.Reader (ReaderT, asks, liftIO, runReaderT)
 import qualified Data.Char as C
 import qualified Data.Maybe as M
 import qualified System.Directory as D
 import qualified System.Process as P
-import           Unused.Cache.FindArgsFromIgnoredPaths (findArgs)
-import           Unused.Util (safeHead, safeReadFile)
+import Unused.Cache.FindArgsFromIgnoredPaths (findArgs)
+import Unused.Util (safeHead, safeReadFile)
 
-newtype MD5ExecutablePath = MD5ExecutablePath { toMD5String :: String }
+newtype MD5ExecutablePath = MD5ExecutablePath
+    { toMD5String :: String
+    }
 
 type MD5Config = ReaderT MD5ExecutablePath IO
 
-data FingerprintOutcome
-    = MD5ExecutableNotFound [String]
+newtype FingerprintOutcome =
+    MD5ExecutableNotFound [String]
 
 sha :: IO (Either FingerprintOutcome String)
 sha = do
     md5Executable' <- md5Executable
     case md5Executable' of
         Just exec ->
-            Right . getSha <$> runReaderT (fileList >>= sortInput >>= md5Result) (MD5ExecutablePath exec)
+            Right . getSha <$>
+            runReaderT (fileList >>= sortInput >>= md5Result) (MD5ExecutablePath exec)
         Nothing -> return $ Left $ MD5ExecutableNotFound supportedMD5Executables
   where
     getSha = takeWhile C.isAlphaNum . M.fromMaybe "" . safeHead . lines
@@ -32,7 +35,9 @@
 fileList = do
     filterNamePathArgs <- liftIO $ findArgs <$> ignoredPaths
     md5exec <- asks toMD5String
-    let args = [".", "-type", "f", "-not", "-path", "*/.git/*"] ++ filterNamePathArgs ++ ["-exec", md5exec, "{}", "+"]
+    let args =
+            [".", "-type", "f", "-not", "-path", "*/.git/*"] ++
+            filterNamePathArgs ++ ["-exec", md5exec, "{}", "+"]
     liftIO $ P.readProcess "find" args ""
 
 sortInput :: String -> MD5Config String
@@ -47,8 +52,7 @@
 ignoredPaths = either (const []) id <$> (fmap lines <$> safeReadFile ".gitignore")
 
 md5Executable :: IO (Maybe String)
-md5Executable =
-    safeHead . concat <$> mapM D.findExecutables supportedMD5Executables
+md5Executable = safeHead . concat <$> mapM D.findExecutables supportedMD5Executables
 
 supportedMD5Executables :: [String]
 supportedMD5Executables = ["md5", "md5sum"]
diff --git a/src/Unused/Cache/FindArgsFromIgnoredPaths.hs b/src/Unused/Cache/FindArgsFromIgnoredPaths.hs
--- a/src/Unused/Cache/FindArgsFromIgnoredPaths.hs
+++ b/src/Unused/Cache/FindArgsFromIgnoredPaths.hs
@@ -38,8 +38,7 @@
 isMissingFilename = null . FP.takeFileName
 
 validIgnoreOptions :: [String] -> [String]
-validIgnoreOptions =
-    filter isPath
+validIgnoreOptions = filter isPath
   where
     isPath "" = False
     isPath ('/':_) = True
diff --git a/src/Unused/Grouping/Internal.hs b/src/Unused/Grouping/Internal.hs
--- a/src/Unused/Grouping/Internal.hs
+++ b/src/Unused/Grouping/Internal.hs
@@ -4,7 +4,8 @@
 
 import qualified Data.List as L
 import qualified System.FilePath as FP
-import           Unused.Grouping.Types (CurrentGrouping(..), Grouping(..), GroupFilter)
+import Unused.Grouping.Types
+       (CurrentGrouping(..), GroupFilter, Grouping(..))
 import qualified Unused.Types as T
 
 groupFilter :: CurrentGrouping -> GroupFilter
@@ -14,5 +15,4 @@
 groupFilter NoGroup = const NoGrouping
 
 shortenedDirectory :: String -> String
-shortenedDirectory =
-    L.intercalate "/" . take 2 . FP.splitDirectories . FP.takeDirectory
+shortenedDirectory = L.intercalate "/" . take 2 . FP.splitDirectories . FP.takeDirectory
diff --git a/src/Unused/Grouping/Types.hs b/src/Unused/Grouping/Types.hs
--- a/src/Unused/Grouping/Types.hs
+++ b/src/Unused/Grouping/Types.hs
@@ -5,12 +5,23 @@
     , GroupFilter
     ) where
 
-import Unused.Types (TermMatchSet, TermMatch)
+import Unused.Types (TermMatch, TermMatchSet)
 
-data Grouping = ByDirectory String | ByTerm String | ByFile String | NoGrouping deriving (Eq, Ord)
-data CurrentGrouping = GroupByDirectory | GroupByTerm | GroupByFile | NoGroup
+data Grouping
+    = ByDirectory String
+    | ByTerm String
+    | ByFile String
+    | NoGrouping
+    deriving (Eq, Ord)
 
+data CurrentGrouping
+    = GroupByDirectory
+    | GroupByTerm
+    | GroupByFile
+    | NoGroup
+
 type GroupedTerms = (Grouping, TermMatchSet)
+
 type GroupFilter = TermMatch -> Grouping
 
 instance Show Grouping where
diff --git a/src/Unused/Parser.hs b/src/Unused/Parser.hs
--- a/src/Unused/Parser.hs
+++ b/src/Unused/Parser.hs
@@ -2,23 +2,24 @@
     ( parseResults
     ) where
 
-import           Control.Arrow ((&&&))
+import Control.Arrow ((&&&))
 import qualified Data.Bifunctor as BF
 import qualified Data.List as L
 import qualified Data.Map.Strict as Map
-import           Unused.Aliases (groupedTermsAndAliases)
-import           Unused.LikelihoodCalculator (calculateLikelihood)
-import           Unused.ResultsClassifier.Types (LanguageConfiguration(..))
-import           Unused.TermSearch (SearchResults, fromResults)
-import           Unused.Types (TermMatchSet, TermMatch, resultsFromMatches, tmDisplayTerm)
+import Unused.Aliases (groupedTermsAndAliases)
+import Unused.LikelihoodCalculator (calculateLikelihood)
+import Unused.ResultsClassifier.Types (LanguageConfiguration(..))
+import Unused.TermSearch (SearchResults, fromResults)
+import Unused.Types
+       (TermMatch, TermMatchSet, resultsFromMatches, tmDisplayTerm)
 
 parseResults :: [LanguageConfiguration] -> SearchResults -> TermMatchSet
 parseResults lcs =
-    Map.fromList . map (BF.second $ calculateLikelihood lcs . resultsFromMatches) . groupResults . fromResults
+    Map.fromList .
+    map (BF.second $ calculateLikelihood lcs . resultsFromMatches) . groupResults . fromResults
 
 groupResults :: [TermMatch] -> [(String, [TermMatch])]
-groupResults ms =
-    map (toKey &&& id) groupedMatches
+groupResults ms = map (toKey &&& id) groupedMatches
   where
     toKey = L.intercalate "|" . L.nub . L.sort . map tmDisplayTerm
     groupedMatches = groupedTermsAndAliases ms
diff --git a/src/Unused/Projection.hs b/src/Unused/Projection.hs
--- a/src/Unused/Projection.hs
+++ b/src/Unused/Projection.hs
@@ -1,12 +1,13 @@
 module Unused.Projection where
 
 import qualified Data.Bifunctor as BF
-import           Data.Monoid ((<>))
-import           Data.Text (Text)
+import Data.Monoid ((<>))
+import Data.Text (Text)
 import qualified Data.Text as T
-import           Text.Megaparsec
-import           Text.Megaparsec.Text
-import           Unused.Projection.Transform
+import Data.Void (Void)
+import Text.Megaparsec
+import Text.Megaparsec.Char
+import Unused.Projection.Transform
 
 data ParsedTransform = ParsedTransform
     { ptPre :: Text
@@ -14,24 +15,20 @@
     , ptPost :: Text
     }
 
+type Parser = Parsec Void Text
+
 translate :: Text -> Either String (Text -> Text)
 translate template = applyTransform <$> parseTransform template
 
 applyTransform :: ParsedTransform -> Text -> Text
-applyTransform pt t =
-    ptPre pt
-    <> runTransformations t (ptTransforms pt)
-    <> ptPost pt
+applyTransform pt t = ptPre pt <> runTransformations t (ptTransforms pt) <> ptPost pt
 
 parseTransform :: Text -> Either String ParsedTransform
 parseTransform = BF.first show . parse parsedTransformParser ""
 
 parsedTransformParser :: Parser ParsedTransform
 parsedTransformParser =
-    ParsedTransform
-    <$> preTransformsParser
-    <*> transformsParser
-    <*> postTransformsParser
+    ParsedTransform <$> preTransformsParser <*> transformsParser <*> postTransformsParser
 
 preTransformsParser :: Parser Text
 preTransformsParser = T.pack <$> manyTill anyChar (char '{')
@@ -45,7 +42,8 @@
 transformParser :: Parser Transform
 transformParser = do
     result <- string "camelcase" <|> string "snakecase"
-    return $ case result of
-        "camelcase" -> Camelcase
-        "snakecase" -> Snakecase
-        _ -> Noop
+    return $
+        case result of
+            "camelcase" -> Camelcase
+            "snakecase" -> Snakecase
+            _ -> Noop
diff --git a/src/Unused/Regex.hs b/src/Unused/Regex.hs
--- a/src/Unused/Regex.hs
+++ b/src/Unused/Regex.hs
@@ -9,5 +9,5 @@
 matchRegex :: String -> String -> Bool
 matchRegex = matchTest . stringToRegex
 
-stringToRegex :: RegexMaker Regex CompOption ExecOption String => String -> Regex
+stringToRegex :: String -> Regex
 stringToRegex = makeRegex
diff --git a/src/Unused/ResponseFilter.hs b/src/Unused/ResponseFilter.hs
--- a/src/Unused/ResponseFilter.hs
+++ b/src/Unused/ResponseFilter.hs
@@ -11,8 +11,12 @@
 import qualified Data.Char as C
 import qualified Data.List as L
 import qualified Data.Map.Strict as Map
-import           Unused.ResultsClassifier (Position(..), Matcher(..), LanguageConfiguration(..), LowLikelihoodMatch(..))
-import           Unused.Types (TermResults(..), TermMatchSet, TermMatch(..), RemovalLikelihood, Removal(..), totalOccurrenceCount, appOccurrenceCount)
+import Unused.ResultsClassifier
+       (LanguageConfiguration(..), LowLikelihoodMatch(..), Matcher(..),
+        Position(..))
+import Unused.Types
+       (Removal(..), RemovalLikelihood, TermMatch(..), TermMatchSet,
+        TermResults(..), appOccurrenceCount, totalOccurrenceCount)
 
 withOneOccurrence :: TermMatchSet -> TermMatchSet
 withOneOccurrence = Map.filterWithKey (const oneOccurence)
@@ -25,8 +29,7 @@
 withLikelihoods l = Map.filterWithKey (const $ includesLikelihood l)
 
 ignoringPaths :: [String] -> TermMatchSet -> TermMatchSet
-ignoringPaths xs =
-    updateMatches newMatches
+ignoringPaths xs = updateMatches newMatches
   where
     newMatches = filter (not . matchesPath . tmPath)
     matchesPath p = any (`L.isInfixOf` p) xs
@@ -35,21 +38,18 @@
 includesLikelihood l = (`elem` l) . rLikelihood . trRemoval
 
 isClassOrModule :: TermResults -> Bool
-isClassOrModule =
-    startsWithUpper . trTerm
+isClassOrModule = startsWithUpper . trTerm
   where
     startsWithUpper [] = False
     startsWithUpper (a:_) = C.isUpper a
 
 autoLowLikelihood :: LanguageConfiguration -> TermResults -> Bool
-autoLowLikelihood l r =
-    isAllowedTerm r allowedTerms || or anySinglesOkay
+autoLowLikelihood l r = isAllowedTerm r allowedTerms || or anySinglesOkay
   where
     allowedTerms = lcAllowedTerms l
     anySinglesOkay = map (\sm -> classOrModule sm r && matchesToBool (smMatchers sm)) singles
     singles = lcAutoLowLikelihood l
     classOrModule = classOrModuleFunction . smClassOrModule
-
     matchesToBool :: [Matcher] -> Bool
     matchesToBool [] = False
     matchesToBool a = all (`matcherToBool` r) a
@@ -73,10 +73,9 @@
 paths = fmap tmPath . trMatches
 
 updateMatches :: ([TermMatch] -> [TermMatch]) -> TermMatchSet -> TermMatchSet
-updateMatches fm =
-    Map.map (updateMatchesWith $ fm . trMatches)
+updateMatches fm = Map.map (updateMatchesWith $ fm . trMatches)
   where
-    updateMatchesWith f tr = tr { trMatches = f tr }
+    updateMatchesWith f tr = tr {trMatches = f tr}
 
 isAllowedTerm :: TermResults -> [String] -> Bool
 isAllowedTerm = elem . trTerm
diff --git a/src/Unused/ResultsClassifier/Config.hs b/src/Unused/ResultsClassifier/Config.hs
--- a/src/Unused/ResultsClassifier/Config.hs
+++ b/src/Unused/ResultsClassifier/Config.hs
@@ -11,12 +11,12 @@
 import qualified Data.FileEmbed as FE
 import qualified Data.Yaml as Y
 import qualified System.Directory as D
-import           System.FilePath ((</>))
-import           Unused.ResultsClassifier.Types (LanguageConfiguration, ParseConfigError(..))
-import           Unused.Util (safeReadFile)
+import System.FilePath ((</>))
+import Unused.ResultsClassifier.Types (LanguageConfiguration, ParseConfigError(..))
+import Unused.Util (safeReadFile)
 
 loadConfig :: Either String [LanguageConfiguration]
-loadConfig = Y.decodeEither defaultConfigFile
+loadConfig = decodeEither defaultConfigFile
 
 defaultConfigFile :: BS.ByteString
 defaultConfigFile = $(FE.embedFile "data/config.yml")
@@ -24,24 +24,21 @@
 loadAllConfigurations :: IO (Either [ParseConfigError] [LanguageConfiguration])
 loadAllConfigurations = do
     homeDir <- D.getHomeDirectory
-
     let defaultConfig = addSourceToLeft "default config" loadConfig
-
     localConfig <- loadConfigFromFile ".unused.yml"
     userConfig <- loadConfigFromFile $ homeDir </> ".unused.yml"
-
     let (lefts, rights) = E.partitionEithers [defaultConfig, localConfig, userConfig]
-
-    return $ if not (null lefts)
-        then Left lefts
-        else Right $ concat rights
+    return $
+        if not (null lefts)
+            then Left lefts
+            else Right $ concat rights
 
 loadConfigFromFile :: String -> IO (Either ParseConfigError [LanguageConfiguration])
 loadConfigFromFile path =
-    either
-        (const $ Right [])
-        (addSourceToLeft path . Y.decodeEither)
-        <$> safeReadFile path
+    either (const $ Right []) (addSourceToLeft path . decodeEither) <$> safeReadFile path
 
 addSourceToLeft :: String -> Either String c -> Either ParseConfigError c
 addSourceToLeft = BF.first . ParseConfigError
+
+decodeEither :: Y.FromJSON a => BS.ByteString -> Either String a
+decodeEither = BF.first Y.prettyPrintParseException . Y.decodeEither'
diff --git a/src/Unused/TagsSource.hs b/src/Unused/TagsSource.hs
--- a/src/Unused/TagsSource.hs
+++ b/src/Unused/TagsSource.hs
@@ -9,7 +9,7 @@
 import qualified Data.List as L
 import qualified Data.Text as T
 import qualified System.Directory as D
-import           Unused.Util (safeReadFile)
+import Unused.Util (safeReadFile)
 
 data TagSearchOutcome
     = TagsFileNotFound [String]
@@ -22,8 +22,7 @@
 loadTagsFromFile = fmap (fmap tokensFromTags) tagsContent
 
 tokensFromTags :: String -> [String]
-tokensFromTags =
-    filter validTokens . L.nub . tokenLocations
+tokensFromTags = filter validTokens . L.nub . tokenLocations
   where
     tokenLocations = map (token . T.splitOn "\t" . T.pack) . lines
     token = T.unpack . head
diff --git a/src/Unused/TermSearch.hs b/src/Unused/TermSearch.hs
--- a/src/Unused/TermSearch.hs
+++ b/src/Unused/TermSearch.hs
@@ -6,24 +6,23 @@
     ) where
 
 import qualified Data.Maybe as M
-import           GHC.IO.Exception (ExitCode(ExitSuccess))
+import GHC.IO.Exception (ExitCode(ExitSuccess))
 import qualified System.Process as P
-import           Unused.TermSearch.Internal (commandLineOptions, parseSearchResult)
-import           Unused.TermSearch.Types (SearchResults(..), SearchBackend(..))
-import           Unused.Types (SearchTerm, searchTermToString)
+import Unused.TermSearch.Internal
+       (commandLineOptions, parseSearchResult)
+import Unused.TermSearch.Types
+       (SearchBackend(..), SearchResults(..))
+import Unused.Types (SearchTerm, searchTermToString)
 
 search :: SearchBackend -> SearchTerm -> IO SearchResults
 search backend t =
-    SearchResults . M.mapMaybe (parseSearchResult backend t) <$> (lines <$> performSearch backend (searchTermToString t))
+    SearchResults . M.mapMaybe (parseSearchResult backend t) <$>
+    (lines <$> performSearch backend (searchTermToString t))
 
 performSearch :: SearchBackend -> String -> IO String
 performSearch b t = extractSearchResults b <$> searchOutcome
   where
-    searchOutcome =
-        P.readProcessWithExitCode
-            (backendToCommand b)
-            (commandLineOptions b t)
-            ""
+    searchOutcome = P.readProcessWithExitCode (backendToCommand b) (commandLineOptions b t) ""
     backendToCommand Rg = "rg"
     backendToCommand Ag = "ag"
 
diff --git a/src/Unused/TermSearch/Internal.hs b/src/Unused/TermSearch/Internal.hs
--- a/src/Unused/TermSearch/Internal.hs
+++ b/src/Unused/TermSearch/Internal.hs
@@ -6,9 +6,9 @@
 import qualified Data.Char as C
 import qualified Data.Maybe as M
 import qualified Data.Text as T
-import           Unused.TermSearch.Types (SearchBackend(..))
-import           Unused.Types (SearchTerm(..), TermMatch(..))
-import           Unused.Util (stringToInt)
+import Unused.TermSearch.Types (SearchBackend(..))
+import Unused.Types (SearchTerm(..), TermMatch(..))
+import Unused.Util (stringToInt)
 
 commandLineOptions :: SearchBackend -> String -> [String]
 commandLineOptions backend t =
@@ -17,8 +17,7 @@
         else nonRegexFlags backend t ++ baseFlags backend
 
 parseSearchResult :: SearchBackend -> SearchTerm -> String -> Maybe TermMatch
-parseSearchResult backend term =
-    maybeTermMatch backend . map T.unpack . T.splitOn ":" . T.pack
+parseSearchResult backend term = maybeTermMatch backend . map T.unpack . T.splitOn ":" . T.pack
   where
     maybeTermMatch Rg [path, count] = Just $ toTermMatch term path $ countInt count
     maybeTermMatch Rg _ = Nothing
diff --git a/src/Unused/TermSearch/Types.hs b/src/Unused/TermSearch/Types.hs
--- a/src/Unused/TermSearch/Types.hs
+++ b/src/Unused/TermSearch/Types.hs
@@ -7,6 +7,10 @@
 
 import Unused.Types (TermMatch)
 
-data SearchBackend = Ag | Rg
+data SearchBackend
+    = Ag
+    | Rg
 
-newtype SearchResults = SearchResults { fromResults :: [TermMatch] } deriving (Monoid)
+newtype SearchResults = SearchResults
+    { fromResults :: [TermMatch]
+    } deriving (Semigroup, Monoid)
diff --git a/src/Unused/Types.hs b/src/Unused/Types.hs
--- a/src/Unused/Types.hs
+++ b/src/Unused/Types.hs
@@ -20,17 +20,19 @@
     , resultAliases
     ) where
 
-import           Control.Monad (liftM2)
-import           Data.Csv (FromRecord, ToRecord)
+import Control.Monad (liftM2)
+import Data.Csv (FromRecord, ToRecord)
 import qualified Data.List as L
-import qualified Data.Maybe as M
 import qualified Data.Map.Strict as Map
+import qualified Data.Maybe as M
 import qualified GHC.Generics as G
 import qualified Unused.Regex as R
 
 data SearchTerm
     = OriginalTerm String
-    | AliasTerm String String deriving (Eq, Show)
+    | AliasTerm String
+                String
+    deriving (Eq, Show)
 
 searchTermToString :: SearchTerm -> String
 searchTermToString (OriginalTerm s) = s
@@ -44,6 +46,7 @@
     } deriving (Eq, Show, G.Generic)
 
 instance FromRecord TermMatch
+
 instance ToRecord TermMatch
 
 data Occurrences = Occurrences
@@ -67,15 +70,21 @@
     , rReason :: String
     } deriving (Eq, Show)
 
-data GitContext = GitContext
+newtype GitContext = GitContext
     { gcCommits :: [GitCommit]
     } deriving (Eq, Show)
 
-data GitCommit = GitCommit
+newtype GitCommit = GitCommit
     { gcSha :: String
     } deriving (Eq, Show)
 
-data RemovalLikelihood = High | Medium | Low | Unknown | NotCalculated deriving (Eq, Show)
+data RemovalLikelihood
+    = High
+    | Medium
+    | Low
+    | Unknown
+    | NotCalculated
+    deriving (Eq, Show)
 
 type TermMatchSet = Map.Map String TermResults
 
@@ -100,15 +109,18 @@
 resultsFromMatches :: [TermMatch] -> TermResults
 resultsFromMatches tms =
     TermResults
-        { trTerm = resultTerm terms
-        , trTerms = L.sort $ L.nub terms
-        , trMatches = tms
-        , trAppOccurrences = appOccurrence
-        , trTestOccurrences = testOccurrence
-        , trTotalOccurrences = Occurrences (sum $ map oFiles [appOccurrence, testOccurrence]) (sum $ map oOccurrences [appOccurrence, testOccurrence])
-        , trRemoval = Removal NotCalculated "Likelihood not calculated"
-        , trGitContext = Nothing
-        }
+    { trTerm = resultTerm terms
+    , trTerms = L.sort $ L.nub terms
+    , trMatches = tms
+    , trAppOccurrences = appOccurrence
+    , trTestOccurrences = testOccurrence
+    , trTotalOccurrences =
+          Occurrences
+              (sum $ map oFiles [appOccurrence, testOccurrence])
+              (sum $ map oOccurrences [appOccurrence, testOccurrence])
+    , trRemoval = Removal NotCalculated "Likelihood not calculated"
+    , trGitContext = Nothing
+    }
   where
     testOccurrence = testOccurrences tms
     appOccurrence = appOccurrences tms
@@ -117,8 +129,7 @@
     resultTerm _ = ""
 
 appOccurrences :: [TermMatch] -> Occurrences
-appOccurrences ms =
-    Occurrences appFiles appOccurrences'
+appOccurrences ms = Occurrences appFiles appOccurrences'
   where
     totalFiles = length $ L.nub $ map tmPath ms
     totalOccurrences = sum $ map tmOccurrences ms
@@ -127,8 +138,7 @@
     appOccurrences' = totalOccurrences - oOccurrences tests
 
 testOccurrences :: [TermMatch] -> Occurrences
-testOccurrences ms =
-    Occurrences totalFiles totalOccurrences
+testOccurrences ms = Occurrences totalFiles totalOccurrences
   where
     testMatches = filter termMatchIsTest ms
     totalFiles = length $ L.nub $ map tmPath testMatches
@@ -144,5 +154,5 @@
 testCamelCaseFilename = R.matchRegex ".*(Spec|Test)\\."
 
 termMatchIsTest :: TermMatch -> Bool
-termMatchIsTest TermMatch{tmPath = path} =
+termMatchIsTest TermMatch {tmPath = path} =
     testDir path || testSnakeCaseFilename path || testCamelCaseFilename path
diff --git a/src/Unused/Util.hs b/src/Unused/Util.hs
--- a/src/Unused/Util.hs
+++ b/src/Unused/Util.hs
@@ -8,18 +8,16 @@
     , safeReadFile
     ) where
 
-import           Control.Arrow ((&&&))
+import Control.Arrow ((&&&))
 import qualified Control.Exception as E
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.ByteString.Lazy.Char8 as Cl8
 import qualified Data.Char as C
-import           Data.Function (on)
+import Data.Function (on)
 import qualified Data.List as L
 
 groupBy :: (Ord b) => (a -> b) -> [a] -> [(b, [a])]
-groupBy f = map (f . head &&& id)
-                   . L.groupBy ((==) `on` f)
-                   . L.sortBy (compare `on` f)
+groupBy f = map (f . head &&& id) . L.groupBy ((==) `on` f) . L.sortBy (compare `on` f)
 
 safeHead :: [a] -> Maybe a
 safeHead (x:_) = Just x
diff --git a/test/Unused/AliasesSpec.hs b/test/Unused/AliasesSpec.hs
--- a/test/Unused/AliasesSpec.hs
+++ b/test/Unused/AliasesSpec.hs
@@ -10,14 +10,18 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $
+spec =
+    parallel $
     describe "termsAndAliases" $ do
         it "returns the terms if no aliases are provided" $
-            termsAndAliases [] ["method_1", "method_2"] `shouldBe` [OriginalTerm "method_1", OriginalTerm "method_2"]
-
+            termsAndAliases [] ["method_1", "method_2"] `shouldBe`
+            [OriginalTerm "method_1", OriginalTerm "method_2"]
         it "adds aliases to the list of terms" $ do
             let predicateAlias = TermAlias "*?" "be_{}" ("be_" <>)
             let pluralizeAlias = TermAlias "really_*" "very_{}" ("very_" <>)
-
-            termsAndAliases [predicateAlias, pluralizeAlias] ["awesome?", "really_cool"]
-                `shouldBe` [OriginalTerm "awesome?", AliasTerm "awesome?" "be_awesome", OriginalTerm "really_cool", AliasTerm "really_cool" "very_cool"]
+            termsAndAliases [predicateAlias, pluralizeAlias] ["awesome?", "really_cool"] `shouldBe`
+                [ OriginalTerm "awesome?"
+                , AliasTerm "awesome?" "be_awesome"
+                , OriginalTerm "really_cool"
+                , AliasTerm "really_cool" "very_cool"
+                ]
diff --git a/test/Unused/Cache/FindArgsFromIgnoredPathsSpec.hs b/test/Unused/Cache/FindArgsFromIgnoredPathsSpec.hs
--- a/test/Unused/Cache/FindArgsFromIgnoredPathsSpec.hs
+++ b/test/Unused/Cache/FindArgsFromIgnoredPathsSpec.hs
@@ -10,18 +10,24 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $
+spec =
+    parallel $
     describe "findArgs" $ do
         it "converts paths" $
-            findArgs ["a/*", "/b/*", "c/"] `shouldBe` [ "-not", "-path", "*/a/*"
-                                                      , "-not", "-path", "*/b/*"
-                                                      , "-not", "-path", "*/c/*"]
-
+            findArgs ["a/*", "/b/*", "c/"] `shouldBe`
+            ["-not", "-path", "*/a/*", "-not", "-path", "*/b/*", "-not", "-path", "*/c/*"]
         it "converts wildcards" $
-            findArgs ["a/*.csv", "/b/*.csv"] `shouldBe` [ "-not", "-path", "*/a/*.csv"
-                                                        , "-not", "-path", "*/b/*.csv"]
-
+            findArgs ["a/*.csv", "/b/*.csv"] `shouldBe`
+            ["-not", "-path", "*/a/*.csv", "-not", "-path", "*/b/*.csv"]
         it "filenames and paths at the same time" $
-            findArgs ["/.foreman", ".bundle/"] `shouldBe` [ "-not", "-name", "*/.foreman"
-                                                          , "-not", "-path", "*/.foreman/*"
-                                                          , "-not", "-path", "*/.bundle/*"]
+            findArgs ["/.foreman", ".bundle/"] `shouldBe`
+            [ "-not"
+            , "-name"
+            , "*/.foreman"
+            , "-not"
+            , "-path"
+            , "*/.foreman/*"
+            , "-not"
+            , "-path"
+            , "*/.bundle/*"
+            ]
diff --git a/test/Unused/Grouping/InternalSpec.hs b/test/Unused/Grouping/InternalSpec.hs
--- a/test/Unused/Grouping/InternalSpec.hs
+++ b/test/Unused/Grouping/InternalSpec.hs
@@ -12,24 +12,18 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $
+spec =
+    parallel $
     describe "groupFilter" $ do
         it "groups by directory" $ do
             let termMatch = TermMatch "AwesomeClass" "foo/bar/baz/buzz.rb" Nothing 10
-
             groupFilter GroupByDirectory termMatch `shouldBe` ByDirectory "foo/bar"
-
         it "groups by term" $ do
             let termMatch = TermMatch "AwesomeClass" "foo/bar/baz/buzz.rb" Nothing 10
-
             groupFilter GroupByTerm termMatch `shouldBe` ByTerm "AwesomeClass"
-
         it "groups by file" $ do
             let termMatch = TermMatch "AwesomeClass" "foo/bar/baz/buzz.rb" Nothing 10
-
             groupFilter GroupByFile termMatch `shouldBe` ByFile "foo/bar/baz/buzz.rb"
-
         it "groups by nothing" $ do
             let termMatch = TermMatch "AwesomeClass" "foo/bar/baz/buzz.rb" Nothing 10
-
             groupFilter NoGroup termMatch `shouldBe` NoGrouping
diff --git a/test/Unused/LikelihoodCalculatorSpec.hs b/test/Unused/LikelihoodCalculatorSpec.hs
--- a/test/Unused/LikelihoodCalculatorSpec.hs
+++ b/test/Unused/LikelihoodCalculatorSpec.hs
@@ -12,52 +12,55 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $
+spec =
+    parallel $
     describe "calculateLikelihood" $ do
         it "prefers language-specific checks first" $ do
-            let railsMatches = [ TermMatch "ApplicationController" "app/controllers/application_controller.rb" Nothing 1 ]
+            let railsMatches =
+                    [ TermMatch
+                          "ApplicationController"
+                          "app/controllers/application_controller.rb"
+                          Nothing
+                          1
+                    ]
             removalLikelihood' railsMatches `shouldBe` Low
-
-            let elixirMatches = [ TermMatch "AwesomeView" "web/views/awesome_view.ex" Nothing 1 ]
+            let elixirMatches = [TermMatch "AwesomeView" "web/views/awesome_view.ex" Nothing 1]
             removalLikelihood' elixirMatches `shouldBe` Low
-
         it "weighs widely-used methods as low likelihood" $ do
-            let matches = [ TermMatch "full_name" "app/models/user.rb" Nothing 4
-                          , TermMatch "full_name" "app/views/application/_auth_header.rb" Nothing 1
-                          , TermMatch "full_name" "app/mailers/user_mailer.rb" Nothing 1
-                          , TermMatch "full_name" "spec/models/user_spec.rb" Nothing 10
-                          ]
-
+            let matches =
+                    [ TermMatch "full_name" "app/models/user.rb" Nothing 4
+                    , TermMatch "full_name" "app/views/application/_auth_header.rb" Nothing 1
+                    , TermMatch "full_name" "app/mailers/user_mailer.rb" Nothing 1
+                    , TermMatch "full_name" "spec/models/user_spec.rb" Nothing 10
+                    ]
             removalLikelihood' matches `shouldBe` Low
-
         it "weighs only-occurs-once methods as high likelihood" $ do
-            let matches = [ TermMatch "obscure_method" "app/models/user.rb" Nothing 1 ]
-
+            let matches = [TermMatch "obscure_method" "app/models/user.rb" Nothing 1]
             removalLikelihood' matches `shouldBe` High
-
         it "weighs methods that seem to only be tested and never used as high likelihood" $ do
-            let matches = [ TermMatch "obscure_method" "app/models/user.rb" Nothing 1
-                          , TermMatch "obscure_method" "spec/models/user_spec.rb" Nothing 5
-                          ]
-
+            let matches =
+                    [ TermMatch "obscure_method" "app/models/user.rb" Nothing 1
+                    , TermMatch "obscure_method" "spec/models/user_spec.rb" Nothing 5
+                    ]
             removalLikelihood' matches `shouldBe` High
-
-        it "weighs methods that seem to only be tested and used in one other area as medium likelihood" $ do
-            let matches = [ TermMatch "obscure_method" "app/models/user.rb" Nothing 1
-                          , TermMatch "obscure_method" "app/controllers/user_controller.rb" Nothing 1
-                          , TermMatch "obscure_method" "spec/models/user_spec.rb" Nothing 5
-                          , TermMatch "obscure_method" "spec/controllers/user_controller_spec.rb" Nothing 5
-                          ]
-
+        it
+            "weighs methods that seem to only be tested and used in one other area as medium likelihood" $ do
+            let matches =
+                    [ TermMatch "obscure_method" "app/models/user.rb" Nothing 1
+                    , TermMatch "obscure_method" "app/controllers/user_controller.rb" Nothing 1
+                    , TermMatch "obscure_method" "spec/models/user_spec.rb" Nothing 5
+                    , TermMatch
+                          "obscure_method"
+                          "spec/controllers/user_controller_spec.rb"
+                          Nothing
+                          5
+                    ]
             removalLikelihood' matches `shouldBe` Medium
-
         it "doesn't mis-categorize allowed terms from different languages" $ do
-            let matches = [ TermMatch "t" "web/models/foo.ex" Nothing 1 ]
-
+            let matches = [TermMatch "t" "web/models/foo.ex" Nothing 1]
             removalLikelihood' matches `shouldBe` High
 
 removalLikelihood' :: [TermMatch] -> RemovalLikelihood
-removalLikelihood' =
-    rLikelihood . trRemoval . calculateLikelihood config . resultsFromMatches
+removalLikelihood' = rLikelihood . trRemoval . calculateLikelihood config . resultsFromMatches
   where
     (Right config) = loadConfig
diff --git a/test/Unused/ParserSpec.hs b/test/Unused/ParserSpec.hs
--- a/test/Unused/ParserSpec.hs
+++ b/test/Unused/ParserSpec.hs
@@ -1,64 +1,91 @@
 module Unused.ParserSpec where
 
 import qualified Data.Map.Strict as Map
-import           Test.Hspec
-import           Unused.Parser
-import           Unused.ResultsClassifier
-import           Unused.TermSearch
-import           Unused.Types
+import Test.Hspec
+import Unused.Parser
+import Unused.ResultsClassifier
+import Unused.TermSearch
+import Unused.Types
 
 main :: IO ()
 main = hspec spec
 
 spec :: Spec
-spec = parallel $
+spec =
+    parallel $
     describe "parseResults" $ do
         it "parses from the correct format" $ do
-            let r1Matches = [ TermMatch "method_name" "app/path/foo.rb" Nothing 1
-                            , TermMatch "method_name" "app/path/other.rb" Nothing 5
-                            , TermMatch "method_name" "spec/path/foo_spec.rb" Nothing 10
-                            ]
-            let r1Results = TermResults "method_name" ["method_name"] r1Matches (Occurrences 1 10) (Occurrences 2 6) (Occurrences 3 16) (Removal Low "used frequently") Nothing
-
-            let r2Matches = [ TermMatch "other" "app/path/other.rb" Nothing 1 ]
-            let r2Results = TermResults "other" ["other"] r2Matches (Occurrences 0 0) (Occurrences 1 1) (Occurrences 1 1) (Removal High "occurs once") Nothing
-
+            let r1Matches =
+                    [ TermMatch "method_name" "app/path/foo.rb" Nothing 1
+                    , TermMatch "method_name" "app/path/other.rb" Nothing 5
+                    , TermMatch "method_name" "spec/path/foo_spec.rb" Nothing 10
+                    ]
+            let r1Results =
+                    TermResults
+                        "method_name"
+                        ["method_name"]
+                        r1Matches
+                        (Occurrences 1 10)
+                        (Occurrences 2 6)
+                        (Occurrences 3 16)
+                        (Removal Low "used frequently")
+                        Nothing
+            let r2Matches = [TermMatch "other" "app/path/other.rb" Nothing 1]
+            let r2Results =
+                    TermResults
+                        "other"
+                        ["other"]
+                        r2Matches
+                        (Occurrences 0 0)
+                        (Occurrences 1 1)
+                        (Occurrences 1 1)
+                        (Removal High "occurs once")
+                        Nothing
             let (Right config) = loadConfig
-
             let result = parseResults config $ SearchResults $ r1Matches ++ r2Matches
-
-            result `shouldBe`
-                Map.fromList [ ("method_name", r1Results), ("other", r2Results) ]
-
+            result `shouldBe` Map.fromList [("method_name", r1Results), ("other", r2Results)]
         it "parses when no config is provided" $ do
-            let r1Matches = [ TermMatch "method_name" "app/path/foo.rb" Nothing 1
-                            , TermMatch "method_name" "app/path/other.rb" Nothing 5
-                            , TermMatch "method_name" "spec/path/foo_spec.rb" Nothing 10
-                            ]
-            let r1Results = TermResults "method_name" ["method_name"] r1Matches (Occurrences 1 10) (Occurrences 2 6) (Occurrences 3 16) (Removal Low "used frequently") Nothing
-
+            let r1Matches =
+                    [ TermMatch "method_name" "app/path/foo.rb" Nothing 1
+                    , TermMatch "method_name" "app/path/other.rb" Nothing 5
+                    , TermMatch "method_name" "spec/path/foo_spec.rb" Nothing 10
+                    ]
+            let r1Results =
+                    TermResults
+                        "method_name"
+                        ["method_name"]
+                        r1Matches
+                        (Occurrences 1 10)
+                        (Occurrences 2 6)
+                        (Occurrences 3 16)
+                        (Removal Low "used frequently")
+                        Nothing
             let result = parseResults [] $ SearchResults r1Matches
-
-            result `shouldBe`
-                Map.fromList [ ("method_name", r1Results) ]
-
+            result `shouldBe` Map.fromList [("method_name", r1Results)]
         it "handles aliases correctly" $ do
-            let r1Matches = [ TermMatch "admin?" "app/path/user.rb" Nothing 3 ]
-
-            let r2Matches = [ TermMatch "admin?" "spec/models/user_spec.rb" (Just "be_admin") 2
-                            , TermMatch "admin?" "spec/features/user_promoted_to_admin_spec.rb" (Just "be_admin") 2
-                            ]
-
-
+            let r1Matches = [TermMatch "admin?" "app/path/user.rb" Nothing 3]
+            let r2Matches =
+                    [ TermMatch "admin?" "spec/models/user_spec.rb" (Just "be_admin") 2
+                    , TermMatch
+                          "admin?"
+                          "spec/features/user_promoted_to_admin_spec.rb"
+                          (Just "be_admin")
+                          2
+                    ]
             let (Right config) = loadConfig
             let searchResults = r1Matches ++ r2Matches
-
             let result = parseResults config $ SearchResults searchResults
-
-            let results = TermResults "admin?" ["admin?", "be_admin"] searchResults (Occurrences 2 4) (Occurrences 1 3) (Occurrences 3 7) (Removal Low "used frequently") Nothing
-            result `shouldBe`
-                Map.fromList [ ("admin?|be_admin", results) ]
-
+            let results =
+                    TermResults
+                        "admin?"
+                        ["admin?", "be_admin"]
+                        searchResults
+                        (Occurrences 2 4)
+                        (Occurrences 1 3)
+                        (Occurrences 3 7)
+                        (Removal Low "used frequently")
+                        Nothing
+            result `shouldBe` Map.fromList [("admin?|be_admin", results)]
         it "handles empty input" $ do
             let (Right config) = loadConfig
             let result = parseResults config $ SearchResults []
diff --git a/test/Unused/ProjectionSpec.hs b/test/Unused/ProjectionSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Unused/ProjectionSpec.hs
@@ -0,0 +1,27 @@
+module Unused.ProjectionSpec where
+
+import Data.Text (Text)
+import Test.Hspec
+import Unused.Projection
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+    parallel $
+    describe "translate" $ do
+        it "replaces the text without transforms" $ translate' "foo_{}" "bar" `shouldBe` "foo_bar"
+        it "handles text transformations" $ do
+            translate' "{camelcase}Validator" "proper_email" `shouldBe` "ProperEmailValidator"
+            translate' "{snakecase}" "ProperEmail" `shouldBe` "proper_email"
+            translate' "{camelcase}Validator" "AlreadyCamelcase" `shouldBe`
+                "AlreadyCamelcaseValidator"
+        it "handles unknown transformations" $
+            translate' "{unknown}Validator" "proper_email" `shouldBe` "proper_email"
+
+translate' :: Text -> Text -> Text
+translate' template v =
+    case translate template of
+        Right f -> f v
+        Left _ -> v
diff --git a/test/Unused/ResponseFilterSpec.hs b/test/Unused/ResponseFilterSpec.hs
--- a/test/Unused/ResponseFilterSpec.hs
+++ b/test/Unused/ResponseFilterSpec.hs
@@ -7,134 +7,148 @@
 import Test.Hspec
 import Unused.ResponseFilter
 import Unused.ResultsClassifier
-import Unused.Types (TermMatch(..), TermResults, resultsFromMatches)
+import Unused.Types
+       (TermMatch(..), TermResults, resultsFromMatches)
 
 main :: IO ()
 main = hspec spec
 
 spec :: Spec
-spec = parallel $ do
-    describe "railsAutoLowLikelihood" $ do
-        it "allows controllers" $ do
-            let match = TermMatch "ApplicationController" "app/controllers/application_controller.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            railsAutoLowLikelihood result `shouldBe` True
-
-        it "allows helpers" $ do
-            let match = TermMatch "ApplicationHelper" "app/helpers/application_helper.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            railsAutoLowLikelihood result `shouldBe` True
-
-        it "allows migrations" $ do
-            let match = TermMatch "CreateUsers" "db/migrate/20160101120000_create_users.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            railsAutoLowLikelihood result `shouldBe` True
-
-        it "disallows service objects" $ do
-            let match = TermMatch "CreatePostWithNotifications" "app/services/create_post_with_notifications.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            railsAutoLowLikelihood result `shouldBe` False
-
-        it "disallows methods" $ do
-            let match = TermMatch "my_method" "app/services/create_post_with_notifications.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            railsAutoLowLikelihood result `shouldBe` False
-
-        it "disallows models that occur in migrations" $ do
-            let model = TermMatch "User" "app/models/user.rb" Nothing 1
-            let migration = TermMatch "User" "db/migrate/20160101120000_create_users.rb" Nothing 1
-            let result = resultsFromMatches [model, migration]
-
-            railsAutoLowLikelihood result `shouldBe` False
-
-        it "allows matches intermixed with other results" $ do
-            let appToken = TermMatch "ApplicationHelper" "app/helpers/application_helper.rb" Nothing 1
-            let testToken = TermMatch "ApplicationHelper" "spec/helpers/application_helper_spec.rb" Nothing 10
-            let result = resultsFromMatches [appToken, testToken]
-
-            railsAutoLowLikelihood result `shouldBe` True
-
-    describe "elixirAutoLowLikelihood" $ do
-        it "disallows controllers" $ do
-            let match = TermMatch "PageController" "web/controllers/page_controller.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` False
-
-        it "allows views" $ do
-            let match = TermMatch "PageView" "web/views/page_view.rb" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` True
-
-        it "allows migrations" $ do
-            let match = TermMatch "CreateUsers" "priv/repo/migrations/20160101120000_create_users.exs" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` True
-
-        it "allows tests" $ do
-            let match = TermMatch "UserTest" "test/models/user_test.exs" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` True
-
-        it "allows Mixfile" $ do
-            let match = TermMatch "Mixfile" "mix.exs" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` True
-
-        it "allows __using__" $ do
-            let match = TermMatch "__using__" "web/web.ex" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` True
-
-        it "disallows service modules" $ do
-            let match = TermMatch "CreatePostWithNotifications" "web/services/create_post_with_notifications.ex" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` False
-
-        it "disallows functions" $ do
-            let match = TermMatch "my_function" "web/services/create_post_with_notifications.ex" Nothing 1
-            let result = resultsFromMatches [match]
-
-            elixirAutoLowLikelihood result `shouldBe` False
-
-        it "allows matches intermixed with other results" $ do
-            let appToken = TermMatch "UserView" "web/views/user_view.ex" Nothing 1
-            let testToken = TermMatch "UserView" "test/views/user_view_test.exs" Nothing 10
-            let result = resultsFromMatches [appToken, testToken]
-
-            elixirAutoLowLikelihood result `shouldBe` True
-
-    describe "haskellAutoLowLikelihood" $ do
-        it "allows instance" $ do
-            let match = TermMatch "instance" "src/Lib/Types.hs" Nothing 1
-            let result = resultsFromMatches [match]
-
-            haskellAutoLowLikelihood result `shouldBe` True
-
-        it "allows items in the *.cabal file" $ do
-            let match = TermMatch "Lib.SomethingSpec" "lib.cabal" Nothing 1
-            let result = resultsFromMatches [match]
-
-            haskellAutoLowLikelihood result `shouldBe` True
-
-    describe "autoLowLikelihood" $
-        it "doesn't qualify as low when no matchers are present in a language config" $ do
-            let match = TermMatch "AwesomeThing" "app/foo/awesome_thing.rb" Nothing 1
-            let result = resultsFromMatches [match]
-            let languageConfig = LanguageConfiguration "Bad" [] [LowLikelihoodMatch "Match with empty matchers" [] False] []
-
-            autoLowLikelihood languageConfig result `shouldBe` False
+spec =
+    parallel $ do
+        describe "railsAutoLowLikelihood" $ do
+            it "allows controllers" $ do
+                let match =
+                        TermMatch
+                            "ApplicationController"
+                            "app/controllers/application_controller.rb"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                railsAutoLowLikelihood result `shouldBe` True
+            it "allows helpers" $ do
+                let match =
+                        TermMatch "ApplicationHelper" "app/helpers/application_helper.rb" Nothing 1
+                let result = resultsFromMatches [match]
+                railsAutoLowLikelihood result `shouldBe` True
+            it "allows migrations" $ do
+                let match =
+                        TermMatch
+                            "CreateUsers"
+                            "db/migrate/20160101120000_create_users.rb"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                railsAutoLowLikelihood result `shouldBe` True
+            it "disallows service objects" $ do
+                let match =
+                        TermMatch
+                            "CreatePostWithNotifications"
+                            "app/services/create_post_with_notifications.rb"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                railsAutoLowLikelihood result `shouldBe` False
+            it "disallows methods" $ do
+                let match =
+                        TermMatch
+                            "my_method"
+                            "app/services/create_post_with_notifications.rb"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                railsAutoLowLikelihood result `shouldBe` False
+            it "disallows models that occur in migrations" $ do
+                let model = TermMatch "User" "app/models/user.rb" Nothing 1
+                let migration =
+                        TermMatch "User" "db/migrate/20160101120000_create_users.rb" Nothing 1
+                let result = resultsFromMatches [model, migration]
+                railsAutoLowLikelihood result `shouldBe` False
+            it "allows matches intermixed with other results" $ do
+                let appToken =
+                        TermMatch "ApplicationHelper" "app/helpers/application_helper.rb" Nothing 1
+                let testToken =
+                        TermMatch
+                            "ApplicationHelper"
+                            "spec/helpers/application_helper_spec.rb"
+                            Nothing
+                            10
+                let result = resultsFromMatches [appToken, testToken]
+                railsAutoLowLikelihood result `shouldBe` True
+        describe "elixirAutoLowLikelihood" $ do
+            it "disallows controllers" $ do
+                let match =
+                        TermMatch "PageController" "web/controllers/page_controller.rb" Nothing 1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` False
+            it "allows views" $ do
+                let match = TermMatch "PageView" "web/views/page_view.rb" Nothing 1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` True
+            it "allows migrations" $ do
+                let match =
+                        TermMatch
+                            "CreateUsers"
+                            "priv/repo/migrations/20160101120000_create_users.exs"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` True
+            it "allows tests" $ do
+                let match = TermMatch "UserTest" "test/models/user_test.exs" Nothing 1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` True
+            it "allows Mixfile" $ do
+                let match = TermMatch "Mixfile" "mix.exs" Nothing 1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` True
+            it "allows __using__" $ do
+                let match = TermMatch "__using__" "web/web.ex" Nothing 1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` True
+            it "disallows service modules" $ do
+                let match =
+                        TermMatch
+                            "CreatePostWithNotifications"
+                            "web/services/create_post_with_notifications.ex"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` False
+            it "disallows functions" $ do
+                let match =
+                        TermMatch
+                            "my_function"
+                            "web/services/create_post_with_notifications.ex"
+                            Nothing
+                            1
+                let result = resultsFromMatches [match]
+                elixirAutoLowLikelihood result `shouldBe` False
+            it "allows matches intermixed with other results" $ do
+                let appToken = TermMatch "UserView" "web/views/user_view.ex" Nothing 1
+                let testToken = TermMatch "UserView" "test/views/user_view_test.exs" Nothing 10
+                let result = resultsFromMatches [appToken, testToken]
+                elixirAutoLowLikelihood result `shouldBe` True
+        describe "haskellAutoLowLikelihood" $ do
+            it "allows instance" $ do
+                let match = TermMatch "instance" "src/Lib/Types.hs" Nothing 1
+                let result = resultsFromMatches [match]
+                haskellAutoLowLikelihood result `shouldBe` True
+            it "allows items in the *.cabal file" $ do
+                let match = TermMatch "Lib.SomethingSpec" "lib.cabal" Nothing 1
+                let result = resultsFromMatches [match]
+                haskellAutoLowLikelihood result `shouldBe` True
+        describe "autoLowLikelihood" $
+            it "doesn't qualify as low when no matchers are present in a language config" $ do
+                let match = TermMatch "AwesomeThing" "app/foo/awesome_thing.rb" Nothing 1
+                let result = resultsFromMatches [match]
+                let languageConfig =
+                        LanguageConfiguration
+                            "Bad"
+                            []
+                            [LowLikelihoodMatch "Match with empty matchers" [] False]
+                            []
+                autoLowLikelihood languageConfig result `shouldBe` False
 
 configByName :: String -> LanguageConfiguration
 configByName s = config'
diff --git a/test/Unused/TermSearch/InternalSpec.hs b/test/Unused/TermSearch/InternalSpec.hs
--- a/test/Unused/TermSearch/InternalSpec.hs
+++ b/test/Unused/TermSearch/InternalSpec.hs
@@ -12,28 +12,48 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $ do
-    describe "commandLineOptions" $ do
-        it "does not use regular expressions when the term contains non-word characters" $ do
-            commandLineOptions Ag "can_do_things?" `shouldBe` ["can_do_things?", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
-            commandLineOptions Ag "no_way!" `shouldBe` ["no_way!", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
-            commandLineOptions Ag "[]=" `shouldBe` ["[]=", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
-            commandLineOptions Ag "window.globalOverride" `shouldBe` ["window.globalOverride", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
-
-            commandLineOptions Rg "can_do_things?" `shouldBe` ["can_do_things?", ".", "-F", "-c", "-j", "1"]
-            commandLineOptions Rg "no_way!" `shouldBe` ["no_way!", ".", "-F", "-c", "-j", "1"]
-            commandLineOptions Rg "[]=" `shouldBe` ["[]=", ".", "-F", "-c", "-j", "1"]
-            commandLineOptions Rg "window.globalOverride" `shouldBe` ["window.globalOverride", ".", "-F", "-c", "-j", "1"]
-
-        it "uses regular expression match with surrounding non-word matches for accuracy" $ do
-            commandLineOptions Ag "awesome_method" `shouldBe` ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
-            commandLineOptions Rg "awesome_method" `shouldBe` ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "-j", "1"]
-
-    describe "parseSearchResult" $ do
-        it "parses normal results from `ag` to a TermMatch" $ do
-            parseSearchResult Ag (OriginalTerm "method_name") ":app/models/foo.rb:123" `shouldBe` (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
-            parseSearchResult Rg (OriginalTerm "method_name") "app/models/foo.rb:123" `shouldBe` (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
-
-        it "returns Nothing when it cannot parse" $ do
-            parseSearchResult Ag (OriginalTerm "method_name") "" `shouldBe` Nothing
-            parseSearchResult Rg (OriginalTerm "method_name") "" `shouldBe` Nothing
+spec =
+    parallel $ do
+        describe "commandLineOptions" $ do
+            it "does not use regular expressions when the term contains non-word characters" $ do
+                commandLineOptions Ag "can_do_things?" `shouldBe`
+                    ["can_do_things?", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
+                commandLineOptions Ag "no_way!" `shouldBe`
+                    ["no_way!", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
+                commandLineOptions Ag "[]=" `shouldBe`
+                    ["[]=", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
+                commandLineOptions Ag "window.globalOverride" `shouldBe`
+                    [ "window.globalOverride"
+                    , "."
+                    , "-Q"
+                    , "-c"
+                    , "--ackmate"
+                    , "--ignore-dir"
+                    , "tmp/unused"
+                    ]
+                commandLineOptions Rg "can_do_things?" `shouldBe`
+                    ["can_do_things?", ".", "-F", "-c", "-j", "1"]
+                commandLineOptions Rg "no_way!" `shouldBe` ["no_way!", ".", "-F", "-c", "-j", "1"]
+                commandLineOptions Rg "[]=" `shouldBe` ["[]=", ".", "-F", "-c", "-j", "1"]
+                commandLineOptions Rg "window.globalOverride" `shouldBe`
+                    ["window.globalOverride", ".", "-F", "-c", "-j", "1"]
+            it "uses regular expression match with surrounding non-word matches for accuracy" $ do
+                commandLineOptions Ag "awesome_method" `shouldBe`
+                    [ "(\\W|^)awesome_method(\\W|$)"
+                    , "."
+                    , "-c"
+                    , "--ackmate"
+                    , "--ignore-dir"
+                    , "tmp/unused"
+                    ]
+                commandLineOptions Rg "awesome_method" `shouldBe`
+                    ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "-j", "1"]
+        describe "parseSearchResult" $ do
+            it "parses normal results from `ag` to a TermMatch" $ do
+                parseSearchResult Ag (OriginalTerm "method_name") ":app/models/foo.rb:123" `shouldBe`
+                    (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
+                parseSearchResult Rg (OriginalTerm "method_name") "app/models/foo.rb:123" `shouldBe`
+                    (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
+            it "returns Nothing when it cannot parse" $ do
+                parseSearchResult Ag (OriginalTerm "method_name") "" `shouldBe` Nothing
+                parseSearchResult Rg (OriginalTerm "method_name") "" `shouldBe` Nothing
diff --git a/test/Unused/TypesSpec.hs b/test/Unused/TypesSpec.hs
--- a/test/Unused/TypesSpec.hs
+++ b/test/Unused/TypesSpec.hs
@@ -7,12 +7,29 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $
+spec =
+    parallel $
     describe "resultsFromMatches" $
-        it "batches files together to calculate information" $ do
-            let matches = [ TermMatch "ApplicationController" "app/controllers/application_controller.rb" Nothing 1
-                          , TermMatch "ApplicationController" "spec/controllers/application_controller_spec.rb" Nothing 10
-                          ]
-
-            resultsFromMatches matches `shouldBe`
-                TermResults "ApplicationController" ["ApplicationController"] matches (Occurrences 1 10) (Occurrences 1 1) (Occurrences 2 11) (Removal NotCalculated "Likelihood not calculated") Nothing
+    it "batches files together to calculate information" $ do
+        let matches =
+                [ TermMatch
+                      "ApplicationController"
+                      "app/controllers/application_controller.rb"
+                      Nothing
+                      1
+                , TermMatch
+                      "ApplicationController"
+                      "spec/controllers/application_controller_spec.rb"
+                      Nothing
+                      10
+                ]
+        resultsFromMatches matches `shouldBe`
+            TermResults
+                "ApplicationController"
+                ["ApplicationController"]
+                matches
+                (Occurrences 1 10)
+                (Occurrences 1 1)
+                (Occurrences 2 11)
+                (Removal NotCalculated "Likelihood not calculated")
+                Nothing
diff --git a/test/Unused/UtilSpec.hs b/test/Unused/UtilSpec.hs
--- a/test/Unused/UtilSpec.hs
+++ b/test/Unused/UtilSpec.hs
@@ -15,22 +15,22 @@
 main = hspec spec
 
 spec :: Spec
-spec = parallel $ do
-    describe "groupBy" $ do
-        it "groups by the result of a function" $ do
-            let numbers = [1..10] :: [Int]
-
-            groupBy ((0 ==) . flip mod 2) numbers `shouldBe` [(False, [1, 3, 5, 7, 9]), (True, [2, 4, 6, 8, 10])]
-
-        it "handles records" $ do
-            let people = [Person "Jane" 10, Person "Jane" 20, Person "John" 20]
-
-            groupBy pName people `shouldBe` [("Jane", [Person "Jane" 10, Person "Jane" 20]), ("John", [Person "John" 20])]
-            groupBy pAge people `shouldBe` [(10, [Person "Jane" 10]), (20, [Person "Jane" 20, Person "John" 20])]
-
-    describe "stringToInt" $
-        it "converts a String value to Maybe Int" $ do
-            stringToInt "12345678" `shouldBe` Just 12345678
-            stringToInt "0" `shouldBe` Just 0
-            stringToInt "10591" `shouldBe` Just 10591
-            stringToInt "bad" `shouldBe` Nothing
+spec =
+    parallel $ do
+        describe "groupBy" $ do
+            it "groups by the result of a function" $ do
+                let numbers = [1 .. 10] :: [Int]
+                groupBy ((0 ==) . flip mod 2) numbers `shouldBe`
+                    [(False, [1, 3, 5, 7, 9]), (True, [2, 4, 6, 8, 10])]
+            it "handles records" $ do
+                let people = [Person "Jane" 10, Person "Jane" 20, Person "John" 20]
+                groupBy pName people `shouldBe`
+                    [("Jane", [Person "Jane" 10, Person "Jane" 20]), ("John", [Person "John" 20])]
+                groupBy pAge people `shouldBe`
+                    [(10, [Person "Jane" 10]), (20, [Person "Jane" 20, Person "John" 20])]
+        describe "stringToInt" $
+            it "converts a String value to Maybe Int" $ do
+                stringToInt "12345678" `shouldBe` Just 12345678
+                stringToInt "0" `shouldBe` Just 0
+                stringToInt "10591" `shouldBe` Just 10591
+                stringToInt "bad" `shouldBe` Nothing
diff --git a/unused.cabal b/unused.cabal
--- a/unused.cabal
+++ b/unused.cabal
@@ -1,5 +1,5 @@
 name:                unused
-version:             0.8.0.0
+version:             0.9.0.0
 synopsis:            A command line tool to identify unused code.
 description:         Please see README.md
 homepage:            https://github.com/joshuaclayton/unused#readme
@@ -7,7 +7,7 @@
 license-file:        LICENSE
 author:              Josh Clayton
 maintainer:          sayhi@joshuaclayton.me
-copyright:           2016-2017 Josh Clayton
+copyright:           2016-2018 Josh Clayton
 category:            CLI
 build-type:          Simple
 -- extra-source-files:
@@ -67,7 +67,7 @@
                      , filepath
                      , directory
                      , regex-tdfa
-                     , terminal-progress-bar
+                     , terminal-progress-bar >= 0.1.1.1 && < 0.1.2
                      , ansi-terminal
                      , unix
                      , parallel-io
@@ -75,12 +75,12 @@
                      , bytestring
                      , text
                      , unordered-containers
-                     , cassava >= 0.4.5.0 && < 0.5
+                     , cassava >= 0.5.1.0 && < 0.6
                      , vector
                      , mtl
                      , transformers
-                     , megaparsec >= 5.1.2 && < 6
-                     , inflections >= 0.3.0.0 && < 0.4
+                     , megaparsec >= 6.5.0 && < 7
+                     , inflections >= 0.4.0.3 && < 0.5
                      , file-embed
   ghc-options:         -Wall
   default-language:    Haskell2010
@@ -96,6 +96,7 @@
                      , mtl
                      , transformers
   other-modules:       App
+                     , Types
   default-language:    Haskell2010
 
 test-suite unused-test
@@ -116,6 +117,7 @@
                      , Unused.UtilSpec
                      , Unused.Cache.FindArgsFromIgnoredPathsSpec
                      , Unused.AliasesSpec
+                     , Unused.ProjectionSpec
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall
   default-language:    Haskell2010
   default-extensions:  OverloadedStrings
