{- |
Copyright: (c) 2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>

Static analysis summary.
-}

module Stan.Analysis.Summary
    ( Summary (..)
    , createSummary
    ) where

import Stan.Analysis (Analysis (..))
import Stan.Category (Category)
import Stan.Core.Id (Id)
import Stan.Core.ModuleName (ModuleName)
import Stan.Inspection (Inspection (..))
import Stan.Inspection.All (getInspectionById)
import Stan.Observation (Observation (..))
import Stan.Severity (Severity)

import qualified Data.HashMap.Strict as HM


-- | Short info about analysis.
data Summary = Summary
    { Summary -> Id Inspection
summaryInspectionId :: !(Id Inspection)  -- ^ The most popular 'Inspection'
    , Summary -> Category
summaryCategory     :: !Category  -- ^ The most popular 'Category'
    , Summary -> ModuleName
summaryModule       :: !ModuleName  -- ^ Module with the biggest number of observations
    , Summary -> Severity
summarySeverity     :: !Severity  -- ^ The highest 'Severity'
    }

data SummaryData = SummaryData
    { SummaryData -> HashMap (Id Inspection) Int
sdInspections :: !(HashMap (Id Inspection) Int)  -- ^ Count of each inspection
    , SummaryData -> HashMap Category Int
sdCategories  :: !(HashMap Category Int)  -- ^ Count of each category
    , SummaryData -> HashMap ModuleName Int
sdModules     :: !(HashMap ModuleName Int)  -- ^ Count of observations per module
    , SummaryData -> Maybe Severity
sdSeverity    :: !(Maybe Severity)  -- ^ Highest severity
    }

emptySummaryData :: SummaryData
emptySummaryData :: SummaryData
emptySummaryData = SummaryData
    { sdInspections :: HashMap (Id Inspection) Int
sdInspections = HashMap (Id Inspection) Int
forall a. Monoid a => a
mempty
    , sdCategories :: HashMap Category Int
sdCategories  = HashMap Category Int
forall a. Monoid a => a
mempty
    , sdModules :: HashMap ModuleName Int
sdModules     = HashMap ModuleName Int
forall a. Monoid a => a
mempty
    , sdSeverity :: Maybe Severity
sdSeverity    = Maybe Severity
forall a. Maybe a
Nothing
    }

summaryFromData :: SummaryData -> Maybe Summary
summaryFromData :: SummaryData -> Maybe Summary
summaryFromData SummaryData{Maybe Severity
HashMap Category Int
HashMap (Id Inspection) Int
HashMap ModuleName Int
sdInspections :: SummaryData -> HashMap (Id Inspection) Int
sdCategories :: SummaryData -> HashMap Category Int
sdModules :: SummaryData -> HashMap ModuleName Int
sdSeverity :: SummaryData -> Maybe Severity
sdInspections :: HashMap (Id Inspection) Int
sdCategories :: HashMap Category Int
sdModules :: HashMap ModuleName Int
sdSeverity :: Maybe Severity
..} = do
    Id Inspection
summaryInspectionId <- [(Id Inspection, Int)] -> Maybe (Id Inspection)
forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd ([(Id Inspection, Int)] -> Maybe (Id Inspection))
-> [(Id Inspection, Int)] -> Maybe (Id Inspection)
forall a b. (a -> b) -> a -> b
$ HashMap (Id Inspection) Int -> [(Id Inspection, Int)]
forall k v. HashMap k v -> [(k, v)]
HM.toList HashMap (Id Inspection) Int
sdInspections
    Category
summaryCategory     <- [(Category, Int)] -> Maybe Category
forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd ([(Category, Int)] -> Maybe Category)
-> [(Category, Int)] -> Maybe Category
forall a b. (a -> b) -> a -> b
$ HashMap Category Int -> [(Category, Int)]
forall k v. HashMap k v -> [(k, v)]
HM.toList HashMap Category Int
sdCategories
    ModuleName
summaryModule       <- [(ModuleName, Int)] -> Maybe ModuleName
forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd ([(ModuleName, Int)] -> Maybe ModuleName)
-> [(ModuleName, Int)] -> Maybe ModuleName
forall a b. (a -> b) -> a -> b
$ HashMap ModuleName Int -> [(ModuleName, Int)]
forall k v. HashMap k v -> [(k, v)]
HM.toList HashMap ModuleName Int
sdModules
    Severity
summarySeverity     <- Maybe Severity
sdSeverity
    Summary -> Maybe Summary
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Summary{Category
Id Inspection
ModuleName
Severity
summaryInspectionId :: Id Inspection
summaryCategory :: Category
summaryModule :: ModuleName
summarySeverity :: Severity
summaryInspectionId :: Id Inspection
summaryCategory :: Category
summaryModule :: ModuleName
summarySeverity :: Severity
..}
  where
    maxOnSnd :: Ord b => [(a, b)] -> Maybe a
    maxOnSnd :: forall b a. Ord b => [(a, b)] -> Maybe a
maxOnSnd = (NonEmpty (a, b) -> a) -> [(a, b)] -> Maybe a
forall a b. (NonEmpty a -> b) -> [a] -> Maybe b
viaNonEmpty ((a, b) -> a
forall a b. (a, b) -> a
fst ((a, b) -> a)
-> (NonEmpty (a, b) -> (a, b)) -> NonEmpty (a, b) -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((a, b) -> b) -> NonEmpty (a, b) -> (a, b)
forall b a. Ord b => (a -> b) -> NonEmpty a -> a
maximumOn1 (a, b) -> b
forall a b. (a, b) -> b
snd)

{- | Assemble 'Summary' after analysis. Returns 'Nothing' when there's
no 'Observations'. Otherwise, there's at least one observation, which
means that we can find the most popular 'Inspection', 'Category' and
the highest 'Severity'.
-}
createSummary :: Analysis -> Maybe Summary
createSummary :: Analysis -> Maybe Summary
createSummary Analysis{Int
(Set OnOffExtension, Set SafeHaskellExtension)
FileMap
HashSet (Id Inspection)
Observations
analysisModulesNum :: Int
analysisLinesOfCode :: Int
analysisUsedExtensions :: (Set OnOffExtension, Set SafeHaskellExtension)
analysisInspections :: HashSet (Id Inspection)
analysisObservations :: Observations
analysisIgnoredObservations :: Observations
analysisFileMap :: FileMap
analysisModulesNum :: Analysis -> Int
analysisLinesOfCode :: Analysis -> Int
analysisUsedExtensions :: Analysis -> (Set OnOffExtension, Set SafeHaskellExtension)
analysisInspections :: Analysis -> HashSet (Id Inspection)
analysisObservations :: Analysis -> Observations
analysisIgnoredObservations :: Analysis -> Observations
analysisFileMap :: Analysis -> FileMap
..} = SummaryData -> Maybe Summary
summaryFromData
    (SummaryData -> Maybe Summary) -> SummaryData -> Maybe Summary
forall a b. (a -> b) -> a -> b
$ (SummaryData -> Observation -> SummaryData)
-> SummaryData -> Observations -> SummaryData
forall b a. (b -> a -> b) -> b -> Slist a -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' SummaryData -> Observation -> SummaryData
handleObservation SummaryData
emptySummaryData Observations
analysisObservations
  where
    handleObservation :: SummaryData -> Observation -> SummaryData
    handleObservation :: SummaryData -> Observation -> SummaryData
handleObservation SummaryData{Maybe Severity
HashMap Category Int
HashMap (Id Inspection) Int
HashMap ModuleName Int
sdInspections :: SummaryData -> HashMap (Id Inspection) Int
sdCategories :: SummaryData -> HashMap Category Int
sdModules :: SummaryData -> HashMap ModuleName Int
sdSeverity :: SummaryData -> Maybe Severity
sdInspections :: HashMap (Id Inspection) Int
sdCategories :: HashMap Category Int
sdModules :: HashMap ModuleName Int
sdSeverity :: Maybe Severity
..} Observation{FilePath
ByteString
RealSrcSpan
Id Inspection
Id Observation
ModuleName
observationId :: Id Observation
observationInspectionId :: Id Inspection
observationSrcSpan :: RealSrcSpan
observationFile :: FilePath
observationModuleName :: ModuleName
observationFileContent :: ByteString
observationId :: Observation -> Id Observation
observationInspectionId :: Observation -> Id Inspection
observationSrcSpan :: Observation -> RealSrcSpan
observationFile :: Observation -> FilePath
observationModuleName :: Observation -> ModuleName
observationFileContent :: Observation -> ByteString
..} =
        let Inspection{[Text]
NonEmpty Category
Text
Id Inspection
Severity
InspectionAnalysis
inspectionId :: Id Inspection
inspectionName :: Text
inspectionDescription :: Text
inspectionSolution :: [Text]
inspectionCategory :: NonEmpty Category
inspectionSeverity :: Severity
inspectionAnalysis :: InspectionAnalysis
inspectionId :: Inspection -> Id Inspection
inspectionName :: Inspection -> Text
inspectionDescription :: Inspection -> Text
inspectionSolution :: Inspection -> [Text]
inspectionCategory :: Inspection -> NonEmpty Category
inspectionSeverity :: Inspection -> Severity
inspectionAnalysis :: Inspection -> InspectionAnalysis
..} = Id Inspection -> Inspection
getInspectionById Id Inspection
observationInspectionId
        in SummaryData
            -- increase count for the current observations
            { sdInspections :: HashMap (Id Inspection) Int
sdInspections = (Int -> Int -> Int)
-> Id Inspection
-> Int
-> HashMap (Id Inspection) Int
-> HashMap (Id Inspection) Int
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) Id Inspection
observationInspectionId Int
1 HashMap (Id Inspection) Int
sdInspections

            -- increase count for each category
            , sdCategories :: HashMap Category Int
sdCategories = (Category -> HashMap Category Int -> HashMap Category Int)
-> HashMap Category Int
-> NonEmpty Category
-> HashMap Category Int
forall (f :: * -> *) a b.
Foldable f =>
(a -> b -> b) -> b -> f a -> b
flipfoldl'
                (\Category
cat -> (Int -> Int -> Int)
-> Category -> Int -> HashMap Category Int -> HashMap Category Int
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) Category
cat Int
1)
                HashMap Category Int
sdCategories
                NonEmpty Category
inspectionCategory

            -- increase count the module
            , sdModules :: HashMap ModuleName Int
sdModules = (Int -> Int -> Int)
-> ModuleName
-> Int
-> HashMap ModuleName Int
-> HashMap ModuleName Int
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
HM.insertWith Int -> Int -> Int
forall a. Num a => a -> a -> a
(+) ModuleName
observationModuleName Int
1 HashMap ModuleName Int
sdModules

            -- choose the highest severity
            , sdSeverity :: Maybe Severity
sdSeverity = Maybe Severity -> Maybe Severity -> Maybe Severity
forall a. Ord a => a -> a -> a
max Maybe Severity
sdSeverity (Severity -> Maybe Severity
forall a. a -> Maybe a
Just Severity
inspectionSeverity)
            }

-- | Fast, strict maximum
maximumOn1 :: forall b a . Ord b => (a -> b) -> NonEmpty a -> a
maximumOn1 :: forall b a. Ord b => (a -> b) -> NonEmpty a -> a
maximumOn1 a -> b
f (a
x :| [a]
xs) = (a -> a -> a) -> a -> [a] -> a
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' a -> a -> a
cmp a
x [a]
xs
  where
    cmp :: a -> a -> a
    cmp :: a -> a -> a
cmp a
a a
b = case b -> b -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (a -> b
f a
a) (a -> b
f a
b) of
        Ordering
LT -> a
b
        Ordering
EQ -> a
a
        Ordering
GT -> a
a