Minor reformatting
This commit is contained in:
parent
2f5a7be421
commit
cde1e2966f
|
@ -1,6 +1,6 @@
|
|||
module Shpell.Analytics where
|
||||
|
||||
import Shpell.Parser
|
||||
import Shpell.Parser
|
||||
import Control.Monad
|
||||
import Control.Monad.State
|
||||
import qualified Data.Map as Map
|
||||
|
@ -25,8 +25,8 @@ basicChecks = [
|
|||
modifyMap = modify
|
||||
addNoteFor id note = modifyMap $ Map.adjust (\(Metadata pos notes) -> Metadata pos (note:notes)) id
|
||||
|
||||
willSplit x =
|
||||
case x of
|
||||
willSplit x =
|
||||
case x of
|
||||
T_DollarVariable _ _ -> True
|
||||
T_DollarBraced _ _ -> True
|
||||
T_DollarExpansion _ _ -> True
|
||||
|
@ -59,28 +59,28 @@ deadSimple _ = []
|
|||
verify f s = checkBasic f s == Just True
|
||||
verifyNot f s = checkBasic f s == Just False
|
||||
|
||||
checkBasic f s = case parseShell "-" s of
|
||||
checkBasic f s = case parseShell "-" s of
|
||||
(ParseResult (Just (t, m)) _) -> Just . not $ (notesFromMap $ runBasicAnalysis f t m) == (notesFromMap m)
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
|
||||
prop_checkUuoc = verify checkUuoc "cat foo | grep bar"
|
||||
checkUuoc (T_Pipeline _ (T_Redirecting _ _ f@(T_SimpleCommand id _ _):_:_)) =
|
||||
checkUuoc (T_Pipeline _ (T_Redirecting _ _ f@(T_SimpleCommand id _ _):_:_)) =
|
||||
case deadSimple f of ["cat", _] -> addNoteFor id $ Note InfoC "UUOC: 'cat foo | bar | baz' is better written as 'bar < foo | baz'"
|
||||
_ -> return ()
|
||||
checkUuoc _ = return ()
|
||||
|
||||
|
||||
prop_checkForInQuoted = verify checkForInQuoted "for f in \"$(ls)\"; do echo foo; done"
|
||||
checkForInQuoted (T_ForIn _ f [T_NormalWord _ [T_DoubleQuoted id list]] _) =
|
||||
checkForInQuoted (T_ForIn _ f [T_NormalWord _ [T_DoubleQuoted id list]] _) =
|
||||
when (any willSplit list) $ addNoteFor id $ Note ErrorC $ "Since you double quoted this, it will not word split, and the loop will only run once"
|
||||
checkForInQuoted _ = return ()
|
||||
|
||||
|
||||
prop_checkForInLs = verify checkForInLs "for f in $(ls *.mp3); do mplayer \"$f\"; done"
|
||||
checkForInLs (T_ForIn _ f [T_NormalWord _ [T_DollarExpansion id [x]]] _) =
|
||||
case deadSimple x of ("ls":n) -> let args = (if n == [] then ["*"] else n) in
|
||||
checkForInLs (T_ForIn _ f [T_NormalWord _ [T_DollarExpansion id [x]]] _) =
|
||||
case deadSimple x of ("ls":n) -> let args = (if n == [] then ["*"] else n) in
|
||||
addNoteFor id $ Note WarningC $ "Don't use 'for "++f++" in $(ls " ++ (intercalate " " n) ++ ")'. Use 'for "++f++" in "++ (intercalate " " args) ++ "'"
|
||||
_ -> return ()
|
||||
checkForInLs _ = return ()
|
||||
|
@ -88,10 +88,10 @@ checkForInLs _ = return ()
|
|||
|
||||
prop_checkMissingForQuotes = verify checkMissingForQuotes "for f in *.mp3; do rm $f; done"
|
||||
prop_checkMissingForQuotes2 = verifyNot checkMissingForQuotes "for f in foo bar; do rm $f; done"
|
||||
checkMissingForQuotes (T_ForIn _ f words cmds) =
|
||||
checkMissingForQuotes (T_ForIn _ f words cmds) =
|
||||
if not $ any willSplit words then return () else do
|
||||
mapM_ (doAnalysis (markUnquoted f)) cmds
|
||||
where
|
||||
where
|
||||
markUnquoted f (T_NormalWord _ l) = mapM_ mu l
|
||||
markUnquoted _ _ = return ()
|
||||
mu (T_DollarVariable id s) | s == f = warning id
|
||||
|
@ -102,7 +102,7 @@ checkMissingForQuotes _ = return ()
|
|||
|
||||
|
||||
prop_checkUnquotedExpansions = verify checkUnquotedExpansions "rm $(ls)"
|
||||
checkUnquotedExpansions (T_SimpleCommand _ _ cmds) = mapM_ check cmds
|
||||
checkUnquotedExpansions (T_SimpleCommand _ _ cmds) = mapM_ check cmds
|
||||
where check (T_NormalWord _ [T_DollarExpansion id _]) = addNoteFor id $ Note WarningC "Quote the expansion to prevent word splitting"
|
||||
check _ = return ()
|
||||
checkUnquotedExpansions _ = return ()
|
||||
|
@ -110,16 +110,16 @@ checkUnquotedExpansions _ = return ()
|
|||
prop_checkRedirectToSame = verify checkRedirectToSame "cat foo > foo"
|
||||
prop_checkRedirectToSame2 = verify checkRedirectToSame "cat lol | sed -e 's/a/b/g' > lol"
|
||||
prop_checkRedirectToSame3 = verifyNot checkRedirectToSame "cat lol | sed -e 's/a/b/g' > foo.bar && mv foo.bar lol"
|
||||
checkRedirectToSame s@(T_Pipeline _ list) =
|
||||
checkRedirectToSame s@(T_Pipeline _ list) =
|
||||
mapM_ (\l -> (mapM_ (\x -> doAnalysis (checkOccurences x) l) (getAllRedirs list))) list
|
||||
where checkOccurences (T_NormalWord exceptId x) (T_NormalWord newId y) =
|
||||
where checkOccurences (T_NormalWord exceptId x) (T_NormalWord newId y) =
|
||||
when (x == y && exceptId /= newId) (do
|
||||
let note = Note InfoC $ "Make sure not to read and write the same file in the same pipeline"
|
||||
addNoteFor newId $ note
|
||||
addNoteFor exceptId $ note)
|
||||
checkOccurences _ _ = return ()
|
||||
getAllRedirs l = concatMap (\(T_Redirecting _ ls _) -> concatMap getRedirs ls) l
|
||||
getRedirs (T_FdRedirect _ _ (T_IoFile _ op file)) =
|
||||
getRedirs (T_FdRedirect _ _ (T_IoFile _ op file)) =
|
||||
case op of T_Greater _ -> [file]
|
||||
T_Less _ -> [file]
|
||||
T_DGREAT _ -> [file]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{-# LANGUAGE NoMonomorphismRestriction #-}
|
||||
|
||||
module Shpell.Parser (Token(..), Note(..), Severity(..), parseShell, ParseResult(..), ParseNote(..), notesFromMap, Metadata(..), doAnalysis, doTransform, sortNotes) where
|
||||
module Shpell.Parser (Token(..), Note(..), Severity(..), parseShell, ParseResult(..), ParseNote(..), notesFromMap, Metadata(..), doAnalysis, doTransform, sortNotes) where
|
||||
|
||||
import Text.Parsec
|
||||
import Text.Parsec.Pos (initialPos)
|
||||
|
@ -284,11 +284,11 @@ analyze f i t = do
|
|||
f t
|
||||
return . i $ t
|
||||
|
||||
doAnalysis f t = analyze f id t
|
||||
doAnalysis f t = analyze f id t
|
||||
doTransform i t = runIdentity $ analyze (const $ return ()) i t
|
||||
|
||||
|
||||
lolHax s = Re.subRegex (Re.mkRegex "(Id [0-9]+)") (show s) "(Id 0)"
|
||||
lolHax s = Re.subRegex (Re.mkRegex "(Id [0-9]+)") (show s) "(Id 0)"
|
||||
instance Eq Token where
|
||||
(==) a b = (lolHax a) == (lolHax b)
|
||||
|
||||
|
@ -594,7 +594,7 @@ prop_roflol = isWarning readScript "a &; b"
|
|||
prop_roflol2 = isOk readScript "a & b"
|
||||
readSeparatorOp = do
|
||||
notFollowedBy (g_AND_IF <|> g_DSEMI)
|
||||
f <- (try $ char '&' >> spacing >> char ';' >> parseProblem ErrorC "It's not 'foo &; bar', just 'foo & bar'. " >> return '&')
|
||||
f <- (try $ char '&' >> spacing >> char ';' >> parseProblem ErrorC "It's not 'foo &; bar', just 'foo & bar'. " >> return '&')
|
||||
<|> char ';' <|> char '&'
|
||||
spacing
|
||||
return f
|
||||
|
@ -642,7 +642,7 @@ readAndOr = chainr1 readPipeline $ do
|
|||
op <- g_AND_IF <|> g_OR_IF
|
||||
readLineBreak
|
||||
return $ case op of T_AND_IF id -> T_AndIf id
|
||||
T_OR_IF id -> T_OrIf id
|
||||
T_OR_IF id -> T_OrIf id
|
||||
|
||||
readTerm = do
|
||||
m <- readAndOr
|
||||
|
@ -691,9 +691,9 @@ prop_readIfClause2 = isWarning readIfClause "if false; then; echo oo; fi"
|
|||
prop_readIfClause3 = isWarning readIfClause "if false; then true; else; echo lol fi"
|
||||
readIfClause = do
|
||||
id <- getNextId
|
||||
(condition, action) <- readIfPart
|
||||
elifs <- many readElifPart
|
||||
elses <- option [] readElsePart
|
||||
(condition, action) <- readIfPart
|
||||
elifs <- many readElifPart
|
||||
elses <- option [] readElsePart
|
||||
g_Fi
|
||||
return $ T_IfExpression id ((condition, action):elifs) elses
|
||||
|
||||
|
@ -959,14 +959,14 @@ readScript = do
|
|||
rp p filename contents = Ms.runState (runParserT p initialState filename contents) []
|
||||
|
||||
isWarning :: (ParsecT String (Id, Map.Map Id Metadata, [ParseNote]) (Ms.State [ParseNote]) t) -> String -> Bool
|
||||
isWarning p s = (fst cs) && (not . null . snd $ cs) where cs = checkString p s
|
||||
isWarning p s = (fst cs) && (not . null . snd $ cs) where cs = checkString p s
|
||||
|
||||
isOk :: (ParsecT String (Id, Map.Map Id Metadata, [ParseNote]) (Ms.State [ParseNote]) t) -> String -> Bool
|
||||
isOk p s = (fst cs) && (null . snd $ cs) where cs = checkString p s
|
||||
|
||||
checkString parser string =
|
||||
checkString parser string =
|
||||
case rp (parser >> eof >> getMap) "-" string of
|
||||
(Right (m), n) -> (True, (notesFromMap m) ++ n)
|
||||
(Right (m), n) -> (True, (notesFromMap m) ++ n)
|
||||
(Left _, n) -> (False, n)
|
||||
|
||||
parseWithNotes parser = do
|
||||
|
@ -974,7 +974,7 @@ parseWithNotes parser = do
|
|||
map <- getMap
|
||||
parseNotes <- getParseNotes
|
||||
return (item, map, nub . sortNotes $ parseNotes)
|
||||
|
||||
|
||||
toParseNotes (Metadata pos list) = map (\(Note level note) -> ParseNote pos level note) list
|
||||
notesFromMap map = Map.fold (\x -> (++) (toParseNotes x)) [] map
|
||||
|
||||
|
|
|
@ -5,25 +5,25 @@ import Shpell.Analytics
|
|||
import Data.Maybe
|
||||
import Text.Parsec.Pos
|
||||
|
||||
shpellCheck :: String -> [ShpellComment]
|
||||
shpellCheck script =
|
||||
let (ParseResult result notes) = parseShell "-" script in
|
||||
let allNotes = notes ++ (concat $ maybeToList $ do
|
||||
(tree, map) <- result
|
||||
let newMap = runAllAnalytics tree map
|
||||
return $ notesFromMap newMap
|
||||
)
|
||||
in
|
||||
map formatNote $ sortNotes allNotes
|
||||
|
||||
data ShpellComment = ShpellComment { shpellLine :: Int, shpellColumn :: Int, shpellSeverity :: String, shpellComment :: String }
|
||||
|
||||
|
||||
instance Show ShpellComment where
|
||||
show c = concat ["(", show $ shpellLine c, ",", show $ shpellColumn c, ") ", shpellSeverity c, ": ", shpellComment c]
|
||||
|
||||
shpellCheck script =
|
||||
let (ParseResult result notes) = parseShell "-" script in
|
||||
let allNotes = notes ++ (concat $ maybeToList $ do
|
||||
(tree, map) <- result
|
||||
let newMap = runAllAnalytics tree map
|
||||
return $ notesFromMap newMap
|
||||
)
|
||||
in
|
||||
map formatNote $ sortNotes allNotes
|
||||
|
||||
|
||||
severityToString s =
|
||||
case s of
|
||||
severityToString s =
|
||||
case s of
|
||||
ErrorC -> "error"
|
||||
WarningC -> "warning"
|
||||
InfoC -> "info"
|
||||
|
|
|
@ -46,7 +46,7 @@ doInput filename contents colorFunc = do
|
|||
else do
|
||||
putStrLn ("No comments for " ++ filename)
|
||||
|
||||
cuteIndent comment =
|
||||
cuteIndent comment =
|
||||
(replicate ((shpellColumn comment) - 1) ' ') ++ "^-- " ++ (shpellComment comment)
|
||||
|
||||
getColorFunc = do
|
||||
|
@ -60,6 +60,6 @@ main = do
|
|||
hPutStrLn stderr "shpell -- bash/sh shell script static analysis tool"
|
||||
hPutStrLn stderr "Usage: shpell filenames..."
|
||||
exitFailure
|
||||
else
|
||||
else
|
||||
mapM (\f -> doFile f colors) args
|
||||
|
||||
|
|
Loading…
Reference in New Issue