src/Importify/Resolution/Qualified.hs
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}
-- | Utilities to remove unused qualified imports
module Importify.Resolution.Qualified
( removeUnusedQualifiedImports
) where
import Universum
import Data.List (partition)
import Language.Haskell.Exts (ImportDecl (..), ModuleHead (..),
ModuleName (..), QName (..))
import Language.Haskell.Names (NameInfo (GlobalSymbol), Scoped)
import Language.Haskell.Names.SyntaxUtils (dropAnn)
import Extended.Data.Bool ((==>))
import Importify.Syntax (getImportModuleName, isInsideExport,
scopedNameInfo, scopedNameInfo)
-- | Remove unused @qualified as@ imports, i.e. in one of the next form:
-- @
-- import qualified Data.List
-- import qualified Data.List as L
-- import Data.List as L
-- @
-- This function ignores imports with explicit import lists because it
-- is running after stage where symbols from explicit list removed.
removeUnusedQualifiedImports :: [ImportDecl l]
-> Maybe (ModuleHead l)
-> [Scoped l]
-> [ModuleName ()] -- ^ Unused @import A as B@
-> [ImportDecl l]
removeUnusedQualifiedImports imports moduleHead annotations unusedImplicits =
let (possiblyUnused, others) = partition (possiblyUnusedImport unusedImplicits) imports
isImportNeeded name = isInsideExport moduleHead name
|| isInsideModule annotations name
byModuleName = maybe True isImportNeeded
. fmap dropAnn
. qualifiedName
neededQualified = filter byModuleName possiblyUnused
in neededQualified ++ others
possiblyUnusedImport :: [ModuleName ()] -> ImportDecl l -> Bool
possiblyUnusedImport unusedImplicits decl = isNothing (importSpecs decl)
&& isNotImplicitUnused
where
isNotImplicitUnused = (isJust (importAs decl) && not (importQualified decl))
==> getImportModuleName decl `elem` unusedImplicits
-- | For given import collect qualified name.
-- Qualified names gathered using next scheme:
-- @
-- import A ⇒ Nothing
-- import qualified B ⇒ Just B
-- import qualified C as X ⇒ Just X
-- import D as Y ⇒ Just Y
-- @
-- Used later to determine whether empty @qualified@ import needed or not.
qualifiedName :: ImportDecl l -> Maybe (ModuleName l)
qualifiedName ImportDecl{ importAs = as@(Just _) } = as
qualifiedName ImportDecl{ importQualified = True, .. } = Just importModule
qualifiedName _ = Nothing
isInsideModule :: forall l. [Scoped l] -> ModuleName () -> Bool
isInsideModule annotations moduleName = any isNameUsed annotations
where
isNameUsed :: Scoped l -> Bool
isNameUsed (scopedNameInfo -> nameInfo) = case nameInfo of
GlobalSymbol _ (Qual _ usedName _) -> moduleName == usedName
_ -> False