Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Detect plugin param name collisions #115

Merged
merged 22 commits into from
Dec 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion app/MainHie.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Haskell.Ide.Engine.Monad
import Haskell.Ide.Engine.MonadFunctions
import Haskell.Ide.Engine.Options
import Haskell.Ide.Engine.PluginDescriptor
import Haskell.Ide.Engine.Utils
import Haskell.Ide.Engine.Transport.JsonHttp
import Haskell.Ide.Engine.Transport.JsonStdio
import Haskell.Ide.Engine.Types
Expand Down Expand Up @@ -110,6 +111,10 @@ run opts = do

-- log $ T.pack $ "replPluginInfo:" ++ show replPluginInfo

case validatePlugins plugins of
Just err -> error (pdeErrorMsg err)
Nothing -> return ()

-- launch the dispatcher.
_ <- forkIO (runIdeM (IdeState plugins) (dispatcher cin))

Expand Down Expand Up @@ -142,4 +147,3 @@ getUserHomeDirectory = do
-- whatever it takes.
listener :: TChan ChannelRequest -> IO ()
listener = assert False undefined

2 changes: 2 additions & 0 deletions haskell-ide-engine.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ library
Haskell.Ide.Engine.Transport.JsonHttp
Haskell.Ide.Engine.Transport.JsonStdio
Haskell.Ide.Engine.Types
Haskell.Ide.Engine.Utils
other-modules: Paths_haskell_ide_engine
build-depends: Cabal >= 1.22
, aeson
Expand Down Expand Up @@ -97,6 +98,7 @@ test-suite haskell-ide-test
HaRePluginSpec
JsonStdioSpec
JsonSpec
UtilsSpec
build-depends: base
, aeson
, containers
Expand Down
77 changes: 77 additions & 0 deletions src/Haskell/Ide/Engine/Utils.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{-# LANGUAGE OverloadedStrings #-}

module Haskell.Ide.Engine.Utils
(
validatePlugins
, PluginDescriptionError(..)
, ParamCollision(..)
, ParamOccurence(..)
) where

import Data.List
import Haskell.Ide.Engine.PluginDescriptor
import qualified Data.Map as Map

-- |For all the plugins, validate that parameter names are unique within each
-- command. Otherwise return an error, describing the collisions.
validatePlugins :: Plugins -> Maybe PluginDescriptionError
validatePlugins plugins =
case paramNameCollisions plugins of
[] -> Nothing
collisions -> Just PluginDescriptionError {
pdeCollisions = collisions
, pdeErrorMsg = formatParamNameCollisionErrorMsg collisions
}

data PluginDescriptionError =
PluginDescriptionError {
pdeCollisions :: [ParamCollision]
, pdeErrorMsg :: String
} deriving (Eq, Show)

data ParamCollision = ParamCollision PluginId CommandName ParamName [ParamOccurence] deriving (Eq, Show)
data ParamOccurence = AdditionalParam ParamDescription
| ContextParam ParamDescription AcceptedContext
deriving (Eq, Show)

paramNameCollisions :: Plugins -> [ParamCollision]
paramNameCollisions plugins =
concatMap (\(plId, plDesc) ->
concatMap (paramNameCollisionsForCmd plId . cmdDesc) (pdCommands plDesc)) (Map.toList plugins)

paramNameCollisionsForCmd :: PluginId -> CommandDescriptor -> [ParamCollision]
paramNameCollisionsForCmd plId cmdDescriptor =
let collidingParamNames = findCollidingParamNames cmdDescriptor
collisionSources = map (\param -> ParamCollision plId (cmdName cmdDescriptor) param (paramsByName cmdDescriptor param)) collidingParamNames
in collisionSources

-- |Find all the parameters within the CommandDescriptor that goes by the given ParamName
paramsByName :: CommandDescriptor -> ParamName -> [ParamOccurence]
paramsByName cmdDescriptor paramName =
let matchingParamName param = pName param == paramName
additionalParams = map AdditionalParam $ filter matchingParamName (cmdAdditionalParams cmdDescriptor)
cmdContextParams = concatMap
(\accContext -> map
(\param -> (param, accContext))
(contextMapping accContext))
(cmdContexts cmdDescriptor)
cmdContextParamsWithCollisions = map (uncurry ContextParam) $ filter (\(param, _) -> matchingParamName param) cmdContextParams
in additionalParams ++ cmdContextParamsWithCollisions

findCollidingParamNames :: CommandDescriptor -> [ParamName]
findCollidingParamNames cmdDescriptor =
let cmdContextPNames = nub $ concatMap (map pName . contextMapping) (cmdContexts cmdDescriptor) -- collisions within AcceptedContext should not count
additionalPNames = map pName (cmdAdditionalParams cmdDescriptor)
allPNames = cmdContextPNames ++ additionalPNames
uniquePNames = nub allPNames
in nub (allPNames \\ uniquePNames)

formatParamNameCollisionErrorMsg :: [ParamCollision] -> String
formatParamNameCollisionErrorMsg paramCollisions =
"Error: Parameter name collision in plugin description\n" ++
"Parameter names must be unique for each command. The following collisions were found:\n" ++
concatMap (\(ParamCollision plId cmd paramName occurences) ->
("In " ++ show plId ++ ":" ++ show cmd ++ " the parameter " ++ show paramName ++ " is defined in:\n" ++ unlines (map formatOccurence occurences)))
paramCollisions
where formatOccurence (AdditionalParam paramDesc) = " cmdAdditionalParams = " ++ show paramDesc
formatOccurence (ContextParam paramDesc accContext) = " cmdContexts = [" ++ show accContext ++ "]: " ++ show paramDesc
Loading