filib/codeclimate-shellcheck

View on GitHub
src/CC/ShellCheck/Analyze.hs

Summary

Maintainability
Test Coverage
{-# LANGUAGE OverloadedStrings #-}

module CC.ShellCheck.Analyze where

import           CC.ShellCheck.Fingerprint
import           CC.ShellCheck.Types as CC
import           CC.Types as CC
import           Control.Exception.Base
import qualified Data.Map.Strict as DM
import qualified Data.Text as T
import           ShellCheck.Checker
import           ShellCheck.Interface

--------------------------------------------------------------------------------

-- | Main function for analyzing a shell script.
analyze :: Env -> FilePath -> IO [Issue]
analyze env path = do
  shellScript <- readFile path
  result <- checkScript interface $! checkSpec shellScript
  return $! fromCheckResult env result shellScript
  where
    checkSpec :: String -> CheckSpec
    checkSpec x = emptyCheckSpec { csFilename = path, csScript = x }

    interface :: SystemInterface IO
    interface = SystemInterface
        { siReadFile = defaultInterface
        , siFindSource = error "TODO"
        , siGetConfig = error "TODO"
        }

--------------------------------------------------------------------------------

-- | Builds default IO interface with error handling.
defaultInterface :: FilePath -> IO (Either ErrorMessage String)
defaultInterface path = catch (Right <$> readFile path) handler
  where
    handler :: IOException -> IO (Either ErrorMessage String)
    handler ex = return . Left $! show ex

--------------------------------------------------------------------------------

-- | 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.
defaultRemediationPoints :: Int
defaultRemediationPoints = 50000

--------------------------------------------------------------------------------

-- | Maps Severity to Category.
fromSeverity :: Severity -> Category
fromSeverity ErrorC   = BugRisk
fromSeverity InfoC    = BugRisk
fromSeverity StyleC   = Style
fromSeverity WarningC = BugRisk

--------------------------------------------------------------------------------

-- | Maps CheckResult into issues.
fromCheckResult :: Env -> CheckResult -> String -> [Issue]
fromCheckResult env cr shellScript = fromPositionedComment env shellScript <$> crComments cr

--------------------------------------------------------------------------------

-- | Maps from a PositionedComment to an Issue.
fromPositionedComment :: Env -> String -> PositionedComment -> Issue
fromPositionedComment env shellScript p =
  Issue { _check_name         = checkName
        , _description        = description
        , _categories         = categories
        , _location           = location
        , _remediation_points = remediationPoints
        , _content            = content
        , _other_locations    = Nothing
        , _fingerprint        = issueFingerprint p $ T.pack shellScript
        }
  where
    checkName :: T.Text
    checkName = "SC" <> T.pack (show code)

    description :: T.Text
    description = T.pack desc

    categories :: [Category]
    categories = [fromSeverity severity]

    coords :: CC.Position
    coords = Coords (LineColumn (fromIntegral $ posLine pos) (fromIntegral $ posColumn pos))

    location :: Location
    location = Location (posFile pos) $! PositionBased coords coords

    mapping :: Maybe Mapping
    mapping = DM.lookup checkName env

    remediationPoints :: Maybe Int
    remediationPoints = case mapping of
      Just (Mapping x _) -> Just x
      Nothing            -> Just $! case severity of
        ErrorC   -> 4 * defaultRemediationPoints
        WarningC -> 3 * defaultRemediationPoints
        InfoC    -> 2 * defaultRemediationPoints
        StyleC   -> 1 * defaultRemediationPoints

    content :: Maybe Content
    content = fmap (\(Mapping _ x) -> x) mapping

    pos = pcStartPos p
    comment = pcComment p
    code = cCode comment
    severity = cSeverity comment
    desc = cMessage comment