unused 0.8.0.0 → 0.9.0.0
raw patch · 40 files changed
+710/−599 lines, 40 filesdep ~cassavadep ~inflectionsdep ~megaparsecsetup-changedPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: cassava, inflections, megaparsec, terminal-progress-bar
API changes (from Hackage documentation)
- Unused.Cache: data FingerprintOutcome
- Unused.Cache.DirectoryFingerprint: data FingerprintOutcome
- Unused.Types: data GitCommit
- Unused.Types: data GitContext
+ Unused.Cache: newtype FingerprintOutcome
+ Unused.Cache.DirectoryFingerprint: newtype FingerprintOutcome
+ Unused.Projection: type Parser = Parsec Void Text
+ Unused.TermSearch.Types: instance GHC.Base.Semigroup Unused.TermSearch.Types.SearchResults
+ Unused.Types: newtype GitCommit
+ Unused.Types: newtype GitContext
- Common: (<>) :: Monoid m => m -> m -> m
+ Common: (<>) :: Semigroup a => a -> a -> a
- Unused.CLI.Views.SearchResult.ColumnFormatter: ColumnFormat :: (String -> String) -> (String -> String) -> ColumnFormat
+ Unused.CLI.Views.SearchResult.ColumnFormatter: ColumnFormat :: String -> String -> String -> String -> ColumnFormat
- Unused.CLI.Views.SearchResult.Types: ColumnFormat :: (String -> String) -> (String -> String) -> ColumnFormat
+ Unused.CLI.Views.SearchResult.Types: ColumnFormat :: String -> String -> String -> String -> ColumnFormat
- Unused.CLI.Views.SearchResult.Types: liftIO :: MonadIO m => forall a. IO a -> m a
+ Unused.CLI.Views.SearchResult.Types: liftIO :: MonadIO m => IO a -> m a
- Unused.CLI.Views.SearchResult.Types: runReaderT :: ReaderT k r m a -> r -> m a
+ Unused.CLI.Views.SearchResult.Types: runReaderT :: ReaderT r m a -> r -> m a
- Unused.ResultsClassifier.Types: TermAlias :: String -> String -> (Text -> Text) -> TermAlias
+ Unused.ResultsClassifier.Types: TermAlias :: String -> String -> Text -> Text -> TermAlias
Files
- LICENSE +1/−1
- Setup.hs +1/−0
- app/App.hs +32/−72
- app/Main.hs +47/−66
- app/Types.hs +43/−0
- src/Common.hs +0/−1
- src/Unused/Aliases.hs +8/−8
- src/Unused/CLI/GitContext.hs +4/−3
- src/Unused/CLI/ProgressIndicator/Internal.hs +18/−18
- src/Unused/CLI/ProgressIndicator/Types.hs +7/−11
- src/Unused/CLI/Search.hs +4/−2
- src/Unused/CLI/Views/InvalidConfigError.hs +3/−5
- src/Unused/CLI/Views/NoResultsFound.hs +3/−3
- src/Unused/CLI/Views/SearchResult.hs +8/−10
- src/Unused/Cache/DirectoryFingerprint.hs +14/−10
- src/Unused/Cache/FindArgsFromIgnoredPaths.hs +1/−2
- src/Unused/Grouping/Internal.hs +3/−3
- src/Unused/Grouping/Types.hs +14/−3
- src/Unused/Parser.hs +10/−9
- src/Unused/Projection.hs +15/−17
- src/Unused/Regex.hs +1/−1
- src/Unused/ResponseFilter.hs +11/−12
- src/Unused/ResultsClassifier/Config.hs +12/−15
- src/Unused/TagsSource.hs +2/−3
- src/Unused/TermSearch.hs +9/−10
- src/Unused/TermSearch/Internal.hs +4/−5
- src/Unused/TermSearch/Types.hs +6/−2
- src/Unused/Types.hs +31/−21
- src/Unused/Util.hs +3/−5
- test/Unused/AliasesSpec.hs +10/−6
- test/Unused/Cache/FindArgsFromIgnoredPathsSpec.hs +17/−11
- test/Unused/Grouping/InternalSpec.hs +2/−8
- test/Unused/LikelihoodCalculatorSpec.hs +35/−32
- test/Unused/ParserSpec.hs +70/−43
- test/Unused/ProjectionSpec.hs +27/−0
- test/Unused/ResponseFilterSpec.hs +137/−123
- test/Unused/TermSearch/InternalSpec.hs +45/−25
- test/Unused/TypesSpec.hs +25/−8
- test/Unused/UtilSpec.hs +19/−19
- unused.cabal +8/−6
LICENSE view
@@ -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:
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
app/App.hs view
@@ -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]
app/Main.hs view
@@ -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
+ app/Types.hs view
@@ -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)
src/Common.hs view
@@ -3,7 +3,6 @@ module Common ( (<>) ) where- #if MIN_VERSION_base(4, 8, 0) import Data.Monoid ((<>)) #endif
src/Unused/Aliases.hs view
@@ -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
src/Unused/CLI/GitContext.hs view
@@ -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
src/Unused/CLI/ProgressIndicator/Internal.hs view
@@ -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
src/Unused/CLI/ProgressIndicator/Types.hs view
@@ -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 }
src/Unused/CLI/Search.hs view
@@ -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
src/Unused/CLI/Views/InvalidConfigError.hs view
@@ -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]
src/Unused/CLI/Views/NoResultsFound.hs view
@@ -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]
src/Unused/CLI/Views/SearchResult.hs view
@@ -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
src/Unused/Cache/DirectoryFingerprint.hs view
@@ -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"]
src/Unused/Cache/FindArgsFromIgnoredPaths.hs view
@@ -38,8 +38,7 @@ isMissingFilename = null . FP.takeFileName validIgnoreOptions :: [String] -> [String]-validIgnoreOptions =- filter isPath+validIgnoreOptions = filter isPath where isPath "" = False isPath ('/':_) = True
src/Unused/Grouping/Internal.hs view
@@ -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
src/Unused/Grouping/Types.hs view
@@ -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
src/Unused/Parser.hs view
@@ -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
src/Unused/Projection.hs view
@@ -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
src/Unused/Regex.hs view
@@ -9,5 +9,5 @@ matchRegex :: String -> String -> Bool matchRegex = matchTest . stringToRegex -stringToRegex :: RegexMaker Regex CompOption ExecOption String => String -> Regex+stringToRegex :: String -> Regex stringToRegex = makeRegex
src/Unused/ResponseFilter.hs view
@@ -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
src/Unused/ResultsClassifier/Config.hs view
@@ -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'
src/Unused/TagsSource.hs view
@@ -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
src/Unused/TermSearch.hs view
@@ -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"
src/Unused/TermSearch/Internal.hs view
@@ -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
src/Unused/TermSearch/Types.hs view
@@ -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)
src/Unused/Types.hs view
@@ -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
src/Unused/Util.hs view
@@ -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
test/Unused/AliasesSpec.hs view
@@ -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"+ ]
test/Unused/Cache/FindArgsFromIgnoredPathsSpec.hs view
@@ -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/*"+ ]
test/Unused/Grouping/InternalSpec.hs view
@@ -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
test/Unused/LikelihoodCalculatorSpec.hs view
@@ -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
test/Unused/ParserSpec.hs view
@@ -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 []
+ test/Unused/ProjectionSpec.hs view
@@ -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
test/Unused/ResponseFilterSpec.hs view
@@ -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'
test/Unused/TermSearch/InternalSpec.hs view
@@ -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
test/Unused/TypesSpec.hs view
@@ -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
test/Unused/UtilSpec.hs view
@@ -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
unused.cabal view
@@ -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