{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -- | -- -- Utility for formatting @'Idea'@ data in accordance with the Code Climate -- spec: -- module CC ( printIssue , fromIdea ) where import Data.Aeson (ToJSON(..), (.=), encode, object) import Data.Char (toUpper) import Data.Monoid ((<>)) import Data.Text (Text) import Language.Haskell.Exts.SrcLoc (SrcSpan(..)) import qualified Data.Text as T import qualified Data.ByteString.Lazy.Char8 as C8 import Idea (Idea(..), Severity(..)) data Issue = Issue { issueType :: Text , issueCheckName :: Text , issueDescription :: Text , issueContent :: Text , issueCategories :: [Text] , issueLocation :: Location , issueRemediationPoints :: Int } data Location = Location FilePath Position Position data Position = Position Int Int instance ToJSON Issue where toJSON Issue{..} = object [ "type" .= issueType , "check_name" .= issueCheckName , "description" .= issueDescription , "content" .= object [ "body" .= issueContent ] , "categories" .= issueCategories , "location" .= issueLocation , "remediation_points" .= issueRemediationPoints ] instance ToJSON Location where toJSON (Location path begin end) = object [ "path" .= path , "positions" .= object [ "begin" .= begin , "end" .= end ] ] instance ToJSON Position where toJSON (Position line column) = object [ "line" .= line , "column" .= column ] -- | Print an @'Issue'@ with trailing null-terminator and newline -- -- The trailing newline will be ignored, but makes the output more readable -- printIssue :: Issue -> IO () printIssue = C8.putStrLn . (<> "\0") . encode -- | Convert an hlint @'Idea'@ to a datatype more easily serialized for CC fromIdea :: Idea -> Issue fromIdea Idea{..} = Issue { issueType = "issue" , issueCheckName = "HLint/" <> T.pack (camelize ideaHint) , issueDescription = T.pack ideaHint , issueContent = content ideaFrom ideaTo <> listNotes ideaNote , issueCategories = categories ideaHint , issueLocation = fromSrcSpan ideaSpan , issueRemediationPoints = points ideaSeverity } where content from Nothing = T.unlines [ "Found" , "" , "```" , T.pack from , "```" , "" , "remove it." ] content from (Just to) = T.unlines [ "Found" , "" , "```" , T.pack from , "```" , "" , "Why not" , "" , "```" , T.pack to , "```" ] listNotes [] = "" listNotes notes = T.unlines $ [ "" , "Applying this change:" , "" ] ++ map (("* " <>) . T.pack . show) notes categories _ = ["Style"] points Ignore = 0 points Suggestion = basePoints points Warning = 5 * basePoints points Error = 10 * basePoints fromSrcSpan :: SrcSpan -> Location fromSrcSpan SrcSpan{..} = Location (locationFileName srcSpanFilename) (Position srcSpanStartLine srcSpanStartColumn) (Position srcSpanEndLine srcSpanEndColumn) where locationFileName ('.':'/':x) = x locationFileName x = x camelize :: String -> String camelize = concatMap capitalize . words capitalize :: String -> String capitalize [] = [] capitalize (c:rest) = toUpper c : rest -- "The baseline remediation points value is 50,000, which is the time it takes -- to fix a trivial code style issue like a missing semicolon on a single line, -- including the time for the developer to open the code, make the change, and -- confidently commit the fix. All other remediation points values are expressed -- in multiples of that Basic Remediation Point Value." basePoints :: Int basePoints = 50000