{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TemplateHaskell #-} module Run where import Control.Exception import Control.Monad import Data.Char import Data.Version import Development.GitRev import FastString import GHC import OccName import System.Exit import WithCli import Ast import Files import GHC.Show import Graph import qualified Paths_dead_code_detection as Paths import Utils data Options = Options { sourceDirs :: [FilePath], ignore :: [FilePath], root :: [String], version :: Bool, includeUnderscoreNames :: Bool } deriving (Show, Eq, Generic) instance HasArguments Options run :: IO () run = do let mods = [AddShortOption "sourceDirs" 'i', AddShortOption "ignore" 'e'] withCliModified mods $ \ options -> do when (version options) $ do putStrLn versionOutput throwIO ExitSuccess when (null $ root options) $ die "missing option: --root=STRING" files <- filter (`notElem` ignore options) <$> findHaskellFiles (sourceDirs options) deadNames <- deadNamesFromFiles files (map mkModuleName (root options)) (includeUnderscoreNames options) case deadNames of [] -> return () _ -> do forM_ deadNames putStrLn exitWith $ ExitFailure 1 versionOutput :: String versionOutput = "version: " ++ full where isInGit = $(gitHash) /= "UNKNOWN" full = if isInGit then "version: " ++ showVersion Paths.version ++ "\n" ++ "rev: " ++ $(gitHash) ++ (if $(gitDirty) then " (dirty)" else "") ++ "\n" ++ "branch: " ++ $(gitBranch) else "version: " ++ showVersion Paths.version deadNamesFromFiles :: [FilePath] -> [ModuleName] -> Bool -> IO [String] deadNamesFromFiles files roots includeUnderscoreNames = do ast <- parse files case ast of Left err -> die $ ghcError err Right ast -> case findExports ast roots of Left err -> die err Right rootExports -> do let graph = usedTopLevelNames ast return $ fmap formatName $ removeConstructorNames $ filterUnderScoreNames includeUnderscoreNames $ deadNames graph rootExports where ghcError message = stripSpaces $ unlines $ "Some of the input files produce compile errors." : "ghc says:" : map (" " ++) (lines message) ++ [] filterUnderScoreNames :: Bool -> [Name] -> [Name] filterUnderScoreNames include = if include then id else filter (not . startsWith (== '_')) startsWith :: (Char -> Bool) -> Name -> Bool startsWith p name = case unpackFS $ occNameFS $ occName name of (a : _) -> p a [] -> False removeConstructorNames :: [Name] -> [Name] removeConstructorNames = filter (not . isConstructorName) isConstructorName :: Name -> Bool isConstructorName name = startsWith isUpper name || startsWith (== ':') name