mirror of
https://github.com/joshuaclayton/unused.git
synced 2024-09-11 04:45:28 +03:00
Initial support of aliases based on wildcard matching
Why? ==== Dynamic languages, and Rails in particular, support some fun method creation. One common pattern is, within RSpec, to create matchers dynamically based on predicate methods. Two common examples are: * `#admin?` gets converted to the matcher `#be_admin` * `#has_active_todos?` gets converted to the matcher `#have_active_todos` This especially comes into play when writing page objects with predicate methods. This change introduces the concept of aliases, a way to describe the before/after for these transformations. This introduces a direct swap with a wildcard value (%s), although this may change in the future to support other transformations for pluralization, camel-casing, etc. Externally, aliases are not grouped together by term; however, the underlying counts are summed together, increasing the total occurrences and likely pushing the individual method out of "high" likelihood into "medium" or "low" likelihood. Closes #19.
This commit is contained in:
parent
0dcb06fe70
commit
6ffb098b20
14
app/Main.hs
14
app/Main.hs
@ -12,6 +12,7 @@ import Unused.Grouping (CurrentGrouping(..), groupedResponses)
|
||||
import Unused.CLI (SearchRunner(..), withoutCursor, renderHeader, executeSearch, resetScreen, withInterruptHandler)
|
||||
import qualified Unused.CLI.Views as V
|
||||
import Unused.Cache
|
||||
import Unused.Aliases (termsAndAliases)
|
||||
import Unused.TagsSource
|
||||
|
||||
data Options = Options
|
||||
@ -44,11 +45,12 @@ run options = withoutCursor $ do
|
||||
|
||||
case terms' of
|
||||
(Left e) -> V.missingTagsFileError e
|
||||
(Right terms) -> do
|
||||
renderHeader terms
|
||||
|
||||
(Right terms'') -> do
|
||||
languageConfig <- loadLanguageConfig
|
||||
|
||||
let terms = termsWithAlternatesFromConfig languageConfig terms''
|
||||
|
||||
renderHeader terms
|
||||
results <- withCache options $ executeSearch (oSearchRunner options) terms
|
||||
|
||||
let response = parseResults languageConfig results
|
||||
@ -59,6 +61,12 @@ run options = withoutCursor $ do
|
||||
|
||||
return ()
|
||||
|
||||
termsWithAlternatesFromConfig :: [LanguageConfiguration] -> [String] -> [String]
|
||||
termsWithAlternatesFromConfig lcs =
|
||||
termsAndAliases aliases
|
||||
where
|
||||
aliases = concatMap lcTermAliases lcs
|
||||
|
||||
printResults :: Options -> TermMatchSet -> IO ()
|
||||
printResults options = V.searchResults . groupedResponses (oGrouping options) . optionFilters options
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
- name: Rails
|
||||
aliases:
|
||||
- from: "%s?"
|
||||
to: "be_%s"
|
||||
- from: "has_%s?"
|
||||
to: "have_%s"
|
||||
allowedTerms:
|
||||
# serialization
|
||||
- as_json
|
||||
|
68
src/Unused/Aliases.hs
Normal file
68
src/Unused/Aliases.hs
Normal file
@ -0,0 +1,68 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Unused.Aliases
|
||||
( groupedTermsAndAliases
|
||||
, termsAndAliases
|
||||
) where
|
||||
|
||||
import Data.Tuple (swap)
|
||||
import Data.List (nub, sort, find, (\\))
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Unused.ResultsClassifier.Types
|
||||
import Unused.Types (TermMatch, tmTerm)
|
||||
import Unused.Util (groupBy)
|
||||
|
||||
type Alias = (Text, Text)
|
||||
type GroupedResult = (String, [TermMatch])
|
||||
|
||||
groupedTermsAndAliases :: [TermAlias] -> [TermMatch] -> [[TermMatch]]
|
||||
groupedTermsAndAliases as ms =
|
||||
map snd $ foldl (processResultsWithAliases aliases) [] matchesGroupedByTerm
|
||||
where
|
||||
matchesGroupedByTerm = groupBy tmTerm ms
|
||||
aliases = map toAlias as
|
||||
|
||||
termsAndAliases :: [TermAlias] -> [String] -> [String]
|
||||
termsAndAliases as =
|
||||
nub . map T.unpack . concatMap (allAliases aliases . T.pack)
|
||||
where
|
||||
aliases = map toAlias as
|
||||
allAliases :: [Alias] -> Text -> [Text]
|
||||
allAliases as' term = concatMap (`generateAliases` term) as'
|
||||
|
||||
processResultsWithAliases :: [Alias] -> [GroupedResult] -> GroupedResult -> [GroupedResult]
|
||||
processResultsWithAliases as acc result@(term, matches) =
|
||||
if noAliasesExist
|
||||
then acc ++ [result]
|
||||
else case closestAlias of
|
||||
Nothing -> acc ++ [result]
|
||||
Just alias@(aliasTerm, aliasMatches) -> (acc \\ [alias]) ++ [(aliasTerm, aliasMatches ++ matches)]
|
||||
where
|
||||
packedTerm = T.pack term
|
||||
noAliasesExist = null listOfAliases
|
||||
listOfAliases = nub (concatMap (`aliasesForTerm` packedTerm) as) \\ [packedTerm]
|
||||
closestAlias = find ((`elem` listOfAliases) . T.pack . fst) acc
|
||||
|
||||
toAlias :: TermAlias -> Alias
|
||||
toAlias TermAlias{taFrom = from, taTo = to} = (T.pack from, T.pack to)
|
||||
|
||||
generateAliases :: Alias -> Text -> [Text]
|
||||
generateAliases (from, to) term =
|
||||
toTermWithAlias $ parsePatternForMatch from term
|
||||
where
|
||||
toTermWithAlias (Right (Just match)) = [term, T.replace wildcard match to]
|
||||
toTermWithAlias _ = [term]
|
||||
|
||||
parsePatternForMatch :: Text -> Text -> Either Text (Maybe Text)
|
||||
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
|
||||
|
||||
aliasesForTerm :: Alias -> Text -> [Text]
|
||||
aliasesForTerm a t = nub $ sort $ generateAliases a t ++ generateAliases (swap a) t
|
||||
|
||||
wildcard :: Text
|
||||
wildcard = "%s"
|
@ -3,12 +3,24 @@ module Unused.Parser
|
||||
) where
|
||||
|
||||
import Data.Bifunctor (second)
|
||||
import Control.Arrow ((&&&))
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Unused.Util (groupBy)
|
||||
import Data.List (intercalate, sort, nub)
|
||||
import Unused.TermSearch (SearchResults, fromResults)
|
||||
import Unused.Types (TermMatchSet, resultsFromMatches, tmTerm)
|
||||
import Unused.Types (TermMatchSet, TermMatch, resultsFromMatches, tmTerm)
|
||||
import Unused.LikelihoodCalculator
|
||||
import Unused.ResultsClassifier.Types
|
||||
import Unused.Aliases
|
||||
|
||||
parseResults :: [LanguageConfiguration] -> SearchResults -> TermMatchSet
|
||||
parseResults lcs =
|
||||
Map.fromList . map (second $ calculateLikelihood lcs . resultsFromMatches) . groupBy tmTerm . fromResults
|
||||
Map.fromList . map (second $ calculateLikelihood lcs . resultsFromMatches) . groupResults aliases . fromResults
|
||||
where
|
||||
aliases = concatMap lcTermAliases lcs
|
||||
|
||||
groupResults :: [TermAlias] -> [TermMatch] -> [(String, [TermMatch])]
|
||||
groupResults aliases ms =
|
||||
map (toKey &&& id) groupedMatches
|
||||
where
|
||||
toKey = intercalate "|" . nub . sort . map tmTerm
|
||||
groupedMatches = groupedTermsAndAliases aliases ms
|
||||
|
@ -1,6 +1,7 @@
|
||||
module Unused.ResultsClassifier
|
||||
( LanguageConfiguration(..)
|
||||
, LowLikelihoodMatch(..)
|
||||
, TermAlias(..)
|
||||
, Position(..)
|
||||
, Matcher(..)
|
||||
, loadConfig
|
||||
|
@ -4,6 +4,7 @@
|
||||
module Unused.ResultsClassifier.Types
|
||||
( LanguageConfiguration(..)
|
||||
, LowLikelihoodMatch(..)
|
||||
, TermAlias(..)
|
||||
, Position(..)
|
||||
, Matcher(..)
|
||||
) where
|
||||
@ -20,6 +21,7 @@ data LanguageConfiguration = LanguageConfiguration
|
||||
{ lcName :: String
|
||||
, lcAllowedTerms :: [String]
|
||||
, lcAutoLowLikelihood :: [LowLikelihoodMatch]
|
||||
, lcTermAliases :: [TermAlias]
|
||||
} deriving Show
|
||||
|
||||
data LowLikelihoodMatch = LowLikelihoodMatch
|
||||
@ -28,6 +30,11 @@ data LowLikelihoodMatch = LowLikelihoodMatch
|
||||
, smClassOrModule :: Bool
|
||||
} deriving Show
|
||||
|
||||
data TermAlias = TermAlias
|
||||
{ taFrom :: String
|
||||
, taTo :: String
|
||||
} deriving Show
|
||||
|
||||
data Position = StartsWith | EndsWith | Equals deriving Show
|
||||
data Matcher = Term Position String | Path Position String | AppOccurrences Int | AllowedTerms [String] deriving Show
|
||||
|
||||
@ -36,6 +43,7 @@ instance FromJSON LanguageConfiguration where
|
||||
<$> o .: "name"
|
||||
<*> o .: "allowedTerms"
|
||||
<*> o .: "autoLowLikelihood"
|
||||
<*> o .:? "aliases" .!= []
|
||||
parseJSON _ = mzero
|
||||
|
||||
instance FromJSON LowLikelihoodMatch where
|
||||
@ -45,6 +53,12 @@ instance FromJSON LowLikelihoodMatch where
|
||||
<*> o .:? "classOrModule" .!= False
|
||||
parseJSON _ = mzero
|
||||
|
||||
instance FromJSON TermAlias where
|
||||
parseJSON (Y.Object o) = TermAlias
|
||||
<$> o .: "from"
|
||||
<*> o .: "to"
|
||||
parseJSON _ = mzero
|
||||
|
||||
data MatchHandler a = MatchHandler
|
||||
{ mhKeys :: [String]
|
||||
, mhKeyToMatcher :: T.Text -> Either T.Text (a -> Matcher)
|
||||
|
@ -23,13 +23,29 @@ spec = parallel $
|
||||
let r2Matches = [ TermMatch "other" "app/path/other.rb" 1 ]
|
||||
let r2Results = TermResults "other" r2Matches (Occurrences 0 0) (Occurrences 1 1) (Occurrences 1 1) (Removal High "used once")
|
||||
|
||||
(Right config) <- loadConfig
|
||||
|
||||
let result = parseResults config $ SearchResults $ r1Matches ++ r2Matches
|
||||
|
||||
result `shouldBe`
|
||||
Map.fromList [ ("method_name", r1Results), ("other", r2Results) ]
|
||||
|
||||
it "handles aliases correctly" $ do
|
||||
let r1Matches = [ TermMatch "admin?" "app/path/user.rb" 3 ]
|
||||
|
||||
let r2Matches = [ TermMatch "be_admin" "spec/models/user_spec.rb" 2
|
||||
, TermMatch "be_admin" "spec/features/user_promoted_to_admin_spec.rb" 2
|
||||
]
|
||||
|
||||
|
||||
(Right config) <- loadConfig
|
||||
let searchResults = r1Matches ++ r2Matches
|
||||
|
||||
let result = parseResults config $ SearchResults searchResults
|
||||
|
||||
let results = TermResults "admin?" searchResults (Occurrences 2 4) (Occurrences 1 3) (Occurrences 3 7) (Removal Low "used frequently")
|
||||
result `shouldBe`
|
||||
Map.fromList [ ("method_name", r1Results), ("other", r2Results) ]
|
||||
Map.fromList [ ("admin?|be_admin", results) ]
|
||||
|
||||
it "handles empty input" $ do
|
||||
(Right config) <- loadConfig
|
||||
|
@ -23,6 +23,7 @@ library
|
||||
, Unused.Types
|
||||
, Unused.Util
|
||||
, Unused.Regex
|
||||
, Unused.Aliases
|
||||
, Unused.ResponseFilter
|
||||
, Unused.ResultsClassifier
|
||||
, Unused.ResultsClassifier.Types
|
||||
|
Loading…
Reference in New Issue
Block a user