{-# LANGUAGE FlexibleInstances, FlexibleContexts #-} -- TODO: -- ! User configuration. -- ! Checking for side-effect-less code, e.g. "1;". module Yi.Verifier.JavaScript where import Control.Monad.Writer.Lazy (Writer, mapM_, MonadWriter, tell) import Data.List (map, dropWhile, drop, filter, length, intersperse) import qualified Data.DList as D import Prelude () import Yi.Lexer.Alex (Posn, Tok, tokT, tokPosn) import Yi.Lexer.JavaScript (Token(..), TT) import Yi.Prelude hiding (mapM_) import Yi.Syntax.JavaScript hiding (res) -- * Types data Error = MultipleFunctionDeclaration String [Posn] deriving Eq data Warning = UnreachableCode Posn deriving Eq data Report = Err Error | Warn Warning deriving Eq -- * Instances instance Show Error where show (MultipleFunctionDeclaration n ps) = "Function `" ++ n ++ "' declared more than once: " ++ concat (intersperse ", " $ map show ps) instance Show Warning where show (UnreachableCode pos) = "Unreachable code at " ++ show pos instance Show Report where show (Err e) = "EE " ++ show e show (Warn w) = "WW " ++ show w -- * Main code -- | The main verifier which calls the sub-verifiers. verify :: Tree TT -> Writer (D.DList Report) () verify t = do let topfuns = findFunctions (toList t) checkMultipleFuns topfuns mapM_ (checkUnreachable . funBody) topfuns -- | Given a list of function declarations, checks for multiple function -- declarations, including the functions' subfunctions. checkMultipleFuns :: [Statement TT] -> Writer (D.DList Report) () checkMultipleFuns stmts = do let dupFuns = dupsBy ((ttEq) `on` funName) stmts when (not $ null dupFuns) (say (Err (MultipleFunctionDeclaration (nameOf $ tokT $ funName $ head dupFuns) (map (tokPosn . funName) dupFuns)))) let subFuns = map (findFunctions . funBody) (findFunctions stmts) mapM_ checkMultipleFuns subFuns checkUnreachable :: [Statement TT] -> Writer (D.DList Report) () checkUnreachable stmts = do let afterReturn = dropWhile' (not . isReturn) stmts when (not (null afterReturn)) (say (Warn (UnreachableCode (tokPosn $ firstTok $ head afterReturn)))) -- * Helper functions -- | Given two @Tok t@, compares the @t@s. ttEq :: Eq t => Tok t -> Tok t -> Bool ttEq x y = tokT x == tokT y say :: MonadWriter (D.DList a) m => a -> m () say = tell . D.singleton isReturn :: Statement t -> Bool isReturn (Return {}) = True isReturn _ = False -- | Returns a list of the functions in the given block. findFunctions :: [Statement t] -> [Statement t] findFunctions stmts = [ f | f@(FunDecl {}) <- stmts ] -- | Given a 'FunDecl', returns the token representing the name. funName :: Statement t -> t funName (FunDecl _ n _ _) = n funName _ = undefined -- | Given a 'FunDecl', returns its inner body as a list. funBody :: Statement t -> [Statement t] funBody (FunDecl _ _ _ blk) = case blk of Block _ stmts _ -> toList stmts BlockOne stmt -> [stmt] _ -> [] funBody _ = undefined -- | Given a @ValidName@ returns the string representing the name. nameOf :: Token -> String nameOf (ValidName n) = n nameOf _ = undefined -- * Misc -- | Like 'dropWhile' but drops the first element in the result. dropWhile' :: (a -> Bool) -> [a] -> [a] dropWhile' p xs = let res = dropWhile p xs in if null res then [] else drop 1 res dupsBy :: (a -> a -> Bool) -> [a] -> [a] dupsBy p xs = filter (\x -> length (filter (p x) xs) > 1) xs