purescript-0.7.6.1: src/Language/PureScript/Linter/Imports.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
module Language.PureScript.Linter.Imports (findUnusedImports, Name(..), UsedImports()) where
import Prelude ()
import Prelude.Compat
import qualified Data.Map as M
import Data.Maybe (mapMaybe)
import Data.List ((\\), find, intersect)
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.Writer.Class
import Control.Monad(unless,when)
import Data.Foldable (forM_)
import Language.PureScript.AST.Declarations
import Language.PureScript.AST.SourcePos
import Language.PureScript.Names as P
import Language.PureScript.Errors
import Language.PureScript.Sugar.Names.Env
import Language.PureScript.Sugar.Names.Imports
import qualified Language.PureScript.Constants as C
-- | Imported name used in some type or expression.
data Name = IdentName (Qualified Ident) | IsProperName (Qualified ProperName) | DctorName (Qualified ProperName)
-- | Map of module name to list of imported names from that module which have been used.
type UsedImports = M.Map ModuleName [Name]
-- |
-- Find and warn on any unused import statements (qualified or unqualified)
-- or references in an explicit import list.
--
findUnusedImports :: forall m. (Applicative m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) => Module -> Env -> UsedImports -> m ()
findUnusedImports (Module _ _ _ mdecls mexports) env usedImps = do
imps <- findImports mdecls
forM_ (M.toAscList imps) $ \(mni, decls) -> unless (mni `elem` alwaysUsedModules) $
forM_ decls $ \(ss, declType, qualifierName) ->
censor (onErrorMessages $ addModuleLocError ss) $ unless (qnameUsed qualifierName) $
let names = sugarNames mni ++ M.findWithDefault [] mni usedImps
usedNames = mapMaybe (matchName (typeForDCtor mni) qualifierName) names
usedDctors = mapMaybe (matchDctor qualifierName) names
in case declType of
Implicit -> when (null usedNames) $ tell $ errorMessage $ UnusedImport mni
Explicit declrefs -> do
let idents = mapMaybe runDeclRef declrefs
let diff = idents \\ usedNames
case (length diff, length idents) of
(0, _) -> return ()
(n, m) | n == m -> tell $ errorMessage $ UnusedImport mni
_ -> tell $ errorMessage $ UnusedExplicitImport mni diff
-- If we've not already warned a type is unused, check its data constructors
forM_ (mapMaybe getTypeRef declrefs) $ \(tn, c) -> do
let allCtors = dctorsForType mni tn
when (runProperName tn `elem` usedNames) $ case (c, null $ usedDctors `intersect` allCtors) of
(Nothing, True) -> tell $ errorMessage $ UnusedDctorImport tn
(Just (_:_), True) -> tell $ errorMessage $ UnusedDctorImport tn
(Just ctors, _) ->
let ddiff = ctors \\ usedDctors
in unless (null ddiff) $ tell $ errorMessage $ UnusedDctorExplicitImport tn ddiff
_ -> return ()
return ()
_ -> return ()
where
sugarNames :: ModuleName -> [ Name ]
sugarNames (ModuleName [ProperName n]) | n == C.prelude = [ IdentName $ Qualified Nothing (Ident C.bind) ]
sugarNames _ = []
-- rely on exports being elaborated by this point
alwaysUsedModules :: [ ModuleName ]
alwaysUsedModules = ModuleName [ProperName C.prim] : maybe [] (mapMaybe isExport) mexports
where
isExport (ModuleRef mn) = Just mn
isExport _ = Nothing
qnameUsed :: Maybe ModuleName -> Bool
qnameUsed (Just qn) = qn `elem` alwaysUsedModules
qnameUsed Nothing = False
dtys :: ModuleName -> [((ProperName, [ProperName]), ModuleName)]
dtys mn = maybe [] exportedTypes $ envModuleExports <$> mn `M.lookup` env
dctorsForType :: ModuleName -> ProperName -> [ProperName]
dctorsForType mn tn =
maybe [] getDctors (find matches $ dtys mn)
where
matches ((ty, _),_) = ty == tn
getDctors ((_,ctors),_) = ctors
typeForDCtor :: ModuleName -> ProperName -> Maybe ProperName
typeForDCtor mn pn =
getTy <$> find matches (dtys mn)
where
matches ((_, ctors), _) = pn `elem` ctors
getTy ((ty, _), _) = ty
matchName :: (ProperName -> Maybe ProperName) -> Maybe ModuleName -> Name -> Maybe String
matchName _ qual (IdentName (Qualified q x)) | q == qual = Just $ showIdent x
matchName _ qual (IsProperName (Qualified q x)) | q == qual = Just $ runProperName x
matchName lookupDc qual (DctorName (Qualified q x)) | q == qual = runProperName <$> lookupDc x
matchName _ _ _ = Nothing
matchDctor :: Maybe ModuleName -> Name -> Maybe ProperName
matchDctor qual (DctorName (Qualified q x)) | q == qual = Just x
matchDctor _ _ = Nothing
runDeclRef :: DeclarationRef -> Maybe String
runDeclRef (PositionedDeclarationRef _ _ ref) = runDeclRef ref
runDeclRef (ValueRef ident) = Just $ showIdent ident
runDeclRef (TypeRef pn _) = Just $ runProperName pn
runDeclRef _ = Nothing
getTypeRef :: DeclarationRef -> Maybe (ProperName, Maybe [ProperName])
getTypeRef (PositionedDeclarationRef _ _ ref) = getTypeRef ref
getTypeRef (TypeRef pn x) = Just (pn, x)
getTypeRef _ = Nothing
addModuleLocError :: Maybe SourceSpan -> ErrorMessage -> ErrorMessage
addModuleLocError sp err =
case sp of
Just pos -> withPosition pos err
_ -> err