packages feed

fix-imports-2.4.0: src/FixImports/Types.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE PackageImports #-}
module FixImports.Types (
    module FixImports.Types
    , module GHC.LanguageExtensions.Type
) where
import qualified Control.DeepSeq as DeepSeq
import           Control.DeepSeq (deepseq)
import qualified Data.Either as Either
import           Data.Maybe (fromMaybe)
import qualified Data.Set as Set
import qualified Data.String as String
import qualified System.FilePath as FilePath

import           "ghc-lib-parser" GHC.LanguageExtensions.Type (Extension(..))
import qualified "ghc-lib-parser-ex" Language.Haskell.GhclibParserEx.GHC.Driver.Session


type Error = String

parseExtension :: String -> Maybe Extension
parseExtension =
    Language.Haskell.GhclibParserEx.GHC.Driver.Session.readExtension

-- * ImportLine

data ImportLine = ImportLine {
    importDecl :: !Import
    , importComments :: ![Comment]
    , importSource :: !Source
    } deriving (Show)

instance DeepSeq.NFData ImportLine where
    rnf (ImportLine decl cmts source) =
        decl `seq` cmts `deepseq` source `deepseq` ()

-- | Where did this import come from?
data Source = Local | Package deriving (Eq, Show)

instance DeepSeq.NFData Source where
    rnf _ = ()

-- | A Comment is associated with a particular import line.
data Comment = Comment !CmtPos !String deriving (Show)
data CmtPos = CmtAbove | CmtRight deriving (Show)

instance DeepSeq.NFData Comment where
    rnf (Comment a b) = a `seq` b `seq` ()

-- * Import

data Import = Import {
    _importName :: !ModuleName
    , _importPkgQualifier :: !(Maybe String)
    -- | SOURCE pragma?  There is also ideclSourceSrc, but it looks like it's
    -- just to preserve the exact case or British-ness of the pragma, so I can
    -- discard it.
    , _importIsBoot :: !Bool
    , _importSafe :: !Bool -- ^ safe import
    , _importQualified :: !Bool -- ^ qualified
    , _importAs :: !(Maybe Qualification) -- ^ import as
    , _importHiding :: !Bool -- ^ import list is hiding
    , _importEntities :: !(Maybe [Either Error Entity])
    , _importSpan :: !SrcSpan
    } deriving (Eq, Ord, Show)

instance DeepSeq.NFData Import where
    rnf imp = _importPkgQualifier `deepseq` _importEntities imp `deepseq` ()

data SrcSpan = SrcSpan { _startLine, _startCol, _endLine, _endCol :: !Int }
    deriving (Eq, Ord, Show)

noSpan :: SrcSpan
noSpan = SrcSpan 0 0 0 0

makeImport :: ModuleName -> Import
makeImport name = Import
    { _importName = name
    , _importPkgQualifier = Nothing
    , _importIsBoot = False
    , _importSafe = False
    , _importQualified = False
    , _importAs = Nothing
    , _importHiding = False
    , _importEntities = Nothing
    , _importSpan = noSpan
    }

-- | Get the qualified name from an Import.
importQualification :: Import -> Qualification
importQualification imp =
    fromMaybe (toQual (_importName imp)) (_importAs imp)
    where toQual (ModuleName m) = Qualification m

setQualification :: Qualification -> Import -> Import
setQualification qual imp = imp
    { _importQualified = True
    , _importAs = if toQual (_importName imp) == qual then Nothing
        else Just qual
    } where toQual (ModuleName m) = Qualification m

-- | @import X hiding (x)@ doesn't count as an unqualified import, at least not
-- the kind that I manage.
importUnqualified :: Import -> Bool
importUnqualified imp =
    not (_importQualified imp) && not (_importHiding imp)

-- | Has an import list, but is empty.  This import is a no-op, except for
-- instances.
importEmpty :: Import -> Bool
importEmpty imp = case _importEntities imp of
    Just [] -> True
    _ -> False

-- | If this import has a import list, modify its contents.
importModify :: ([Entity] -> [Entity]) -> Import -> Import
importModify modify imp = case (_importHiding imp, _importEntities imp) of
    (False, Just errEntities) -> imp
        { _importEntities = Just $ map Left errs
            ++ map Right (normalize (modify entities))
        }
        where (errs, entities) = Either.partitionEithers errEntities
    _ -> imp
    where
    -- Keep entities unique and sorted.
    normalize = Set.toList . Set.fromList

-- | An imported entity, e.g. @import X (entity)@.
data Entity = Entity {
    _entityQualifier :: !(Maybe String)
    , _entityVar :: !Name
    , _entityList :: !(Maybe String)
    } deriving (Eq, Ord, Show)

instance DeepSeq.NFData Entity where
    rnf (Entity a b c) = a `deepseq` b `deepseq` c `deepseq` ()

-- | A Qualification is a qualified name minus the actual name.  So it should
-- be the tail of a ModuleName.
newtype Qualification = Qualification String
    deriving (Eq, Ord, Show, DeepSeq.NFData, String.IsString)

-- | An unqualified identifier.
data Name = Name !String | Operator !String
    deriving (Eq, Ord, Show)

instance DeepSeq.NFData Name where
    rnf (Name s) = DeepSeq.rnf s
    rnf (Operator s) = DeepSeq.rnf s

showName :: Name -> String
showName (Name s) = s
showName (Operator s) = "(" <> s <> ")"

newtype ModuleName = ModuleName String
    deriving (Eq, Ord, Show, DeepSeq.NFData, String.IsString)

moduleName :: ModuleName -> String
moduleName (ModuleName n) = n

pathToModule :: FilePath -> ModuleName
pathToModule = ModuleName
    .  map (\c -> if c == '/' then '.' else c) . FilePath.dropExtension

moduleToPath :: ModuleName -> FilePath
moduleToPath (ModuleName name) =
    map (\c -> if c == '.' then '/' else c) name ++ ".hs"