{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-}
module ShellCheck.Checks.Commands (checker, optionalChecks, ShellCheck.Checks.Commands.runTests) where
import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib
import ShellCheck.Data
import ShellCheck.Interface
import ShellCheck.Parser
import ShellCheck.Regex
import Control.Monad
import Control.Monad.RWS
import Data.Char
import Data.Functor.Identity
import Data.List
import Data.Maybe
import qualified Data.Map.Strict as Map
import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)
data CommandName = Exactly String | Basename String
deriving (CommandName -> CommandName -> Bool
(CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool) -> Eq CommandName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: CommandName -> CommandName -> Bool
$c/= :: CommandName -> CommandName -> Bool
== :: CommandName -> CommandName -> Bool
$c== :: CommandName -> CommandName -> Bool
Eq, Eq CommandName
Eq CommandName
-> (CommandName -> CommandName -> Ordering)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> CommandName)
-> (CommandName -> CommandName -> CommandName)
-> Ord CommandName
CommandName -> CommandName -> Bool
CommandName -> CommandName -> Ordering
CommandName -> CommandName -> CommandName
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: CommandName -> CommandName -> CommandName
$cmin :: CommandName -> CommandName -> CommandName
max :: CommandName -> CommandName -> CommandName
$cmax :: CommandName -> CommandName -> CommandName
>= :: CommandName -> CommandName -> Bool
$c>= :: CommandName -> CommandName -> Bool
> :: CommandName -> CommandName -> Bool
$c> :: CommandName -> CommandName -> Bool
<= :: CommandName -> CommandName -> Bool
$c<= :: CommandName -> CommandName -> Bool
< :: CommandName -> CommandName -> Bool
$c< :: CommandName -> CommandName -> Bool
compare :: CommandName -> CommandName -> Ordering
$ccompare :: CommandName -> CommandName -> Ordering
$cp1Ord :: Eq CommandName
Ord)
data CommandCheck =
CommandCheck CommandName (Token -> Analysis)
verify :: CommandCheck -> String -> Bool
verify :: CommandCheck -> String -> Bool
verify CommandCheck
f String
s = Checker -> String -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
verifyNot :: CommandCheck -> String -> Bool
verifyNot CommandCheck
f String
s = Checker -> String -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False
commandChecks :: [CommandCheck]
commandChecks :: [CommandCheck]
commandChecks = [
CommandCheck
checkTr
,CommandCheck
checkFindNameGlob
,CommandCheck
checkExpr
,CommandCheck
checkGrepRe
,CommandCheck
checkTrapQuotes
,CommandCheck
checkReturn
,CommandCheck
checkExit
,CommandCheck
checkFindExecWithSingleArgument
,CommandCheck
checkUnusedEchoEscapes
,CommandCheck
checkInjectableFindSh
,CommandCheck
checkFindActionPrecedence
,CommandCheck
checkMkdirDashPM
,CommandCheck
checkNonportableSignals
,CommandCheck
checkInteractiveSu
,CommandCheck
checkSshCommandString
,CommandCheck
checkPrintfVar
,CommandCheck
checkUuoeCmd
,CommandCheck
checkSetAssignment
,CommandCheck
checkExportedExpansions
,CommandCheck
checkAliasesUsesArgs
,CommandCheck
checkAliasesExpandEarly
,CommandCheck
checkUnsetGlobs
,CommandCheck
checkFindWithoutPath
,CommandCheck
checkTimeParameters
,CommandCheck
checkTimedCommand
,CommandCheck
checkLocalScope
,CommandCheck
checkDeprecatedTempfile
,CommandCheck
checkDeprecatedEgrep
,CommandCheck
checkDeprecatedFgrep
,CommandCheck
checkWhileGetoptsCase
,CommandCheck
checkCatastrophicRm
,CommandCheck
checkLetUsage
,CommandCheck
checkMvArguments, CommandCheck
checkCpArguments, CommandCheck
checkLnArguments
,CommandCheck
checkFindRedirections
,CommandCheck
checkReadExpansions
,CommandCheck
checkSudoRedirect
,CommandCheck
checkSudoArgs
,CommandCheck
checkSourceArgs
,CommandCheck
checkChmodDashr
,CommandCheck
checkXargsDashi
,CommandCheck
checkUnquotedEchoSpaces
,CommandCheck
checkEvalArray
]
[CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ (String -> CommandCheck) -> [String] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map String -> CommandCheck
checkArgComparison [String]
declaringCommands
[CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ (String -> CommandCheck) -> [String] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map String -> CommandCheck
checkMaskedReturns [String]
declaringCommands
optionalChecks :: [CheckDescription]
optionalChecks = ((CheckDescription, CommandCheck) -> CheckDescription)
-> [(CheckDescription, CommandCheck)] -> [CheckDescription]
forall a b. (a -> b) -> [a] -> [b]
map (CheckDescription, CommandCheck) -> CheckDescription
forall a b. (a, b) -> a
fst [(CheckDescription, CommandCheck)]
optionalCommandChecks
optionalCommandChecks :: [(CheckDescription, CommandCheck)]
optionalCommandChecks :: [(CheckDescription, CommandCheck)]
optionalCommandChecks = [
(CheckDescription
newCheckDescription {
cdName :: String
cdName = String
"deprecate-which",
cdDescription :: String
cdDescription = String
"Suggest 'command -v' instead of 'which'",
cdPositive :: String
cdPositive = String
"which javac",
cdNegative :: String
cdNegative = String
"command -v javac"
}, CommandCheck
checkWhich)
]
optionalCheckMap :: Map String CommandCheck
optionalCheckMap = [(String, CommandCheck)] -> Map String CommandCheck
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, CommandCheck)] -> Map String CommandCheck)
-> [(String, CommandCheck)] -> Map String CommandCheck
forall a b. (a -> b) -> a -> b
$ ((CheckDescription, CommandCheck) -> (String, CommandCheck))
-> [(CheckDescription, CommandCheck)] -> [(String, CommandCheck)]
forall a b. (a -> b) -> [a] -> [b]
map (\(CheckDescription
desc, CommandCheck
check) -> (CheckDescription -> String
cdName CheckDescription
desc, CommandCheck
check)) [(CheckDescription, CommandCheck)]
optionalCommandChecks
prop_verifyOptionalExamples :: Bool
prop_verifyOptionalExamples = ((CheckDescription, CommandCheck) -> Bool)
-> [(CheckDescription, CommandCheck)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (CheckDescription, CommandCheck) -> Bool
check [(CheckDescription, CommandCheck)]
optionalCommandChecks
where
check :: (CheckDescription, CommandCheck) -> Bool
check (CheckDescription
desc, CommandCheck
check) =
CommandCheck -> String -> Bool
verify CommandCheck
check (CheckDescription -> String
cdPositive CheckDescription
desc)
Bool -> Bool -> Bool
&& CommandCheck -> String -> Bool
verifyNot CommandCheck
check (CheckDescription -> String
cdNegative CheckDescription
desc)
checkGetOpts :: String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
str [String]
flags [String]
args [Token] -> Maybe [(String, (a, Token))]
f =
[String]
flags [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String]
actualFlags Bool -> Bool -> Bool
&& [String]
args [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String]
actualArgs
where
toTokens :: String -> [Token]
toTokens = (String -> Token) -> [String] -> [Token]
forall a b. (a -> b) -> [a] -> [b]
map (Id -> String -> Token
T_Literal (Int -> Id
Id Int
0)) ([String] -> [Token]) -> (String -> [String]) -> String -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String]
words
opts :: [(String, (a, Token))]
opts = [(String, (a, Token))]
-> Maybe [(String, (a, Token))] -> [(String, (a, Token))]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [(String, (a, Token))] -> [(String, (a, Token))])
-> Maybe [(String, (a, Token))] -> [(String, (a, Token))]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe [(String, (a, Token))]
f (String -> [Token]
toTokens String
str)
actualFlags :: [String]
actualFlags = (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null) ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$ ((String, (a, Token)) -> String)
-> [(String, (a, Token))] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String, (a, Token)) -> String
forall a b. (a, b) -> a
fst [(String, (a, Token))]
opts
actualArgs :: [String]
actualArgs = [Token -> String
onlyLiteralString Token
x | ("", (_, x)) <- [(String, (a, Token))]
opts]
prop_checkGetOptsS1 :: Bool
prop_checkGetOptsS1 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-f x" [String
"f"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"f:" []
prop_checkGetOptsS2 :: Bool
prop_checkGetOptsS2 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-fx" [String
"f"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"f:" []
prop_checkGetOptsS3 :: Bool
prop_checkGetOptsS3 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-f -x" [String
"f", String
"x"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"fx" []
prop_checkGetOptsS4 :: Bool
prop_checkGetOptsS4 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-f -x" [String
"f"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"f:" []
prop_checkGetOptsS5 :: Bool
prop_checkGetOptsS5 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-fx" [] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"fx:" []
prop_checkGenericOptsS1 :: Bool
prop_checkGenericOptsS1 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-f x" [String
"f"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGenericOptsS2 :: Bool
prop_checkGenericOptsS2 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-abc x" [String
"a", String
"b", String
"c"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGenericOptsS3 :: Bool
prop_checkGenericOptsS3 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-abc -x" [String
"a", String
"b", String
"c", String
"x"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGenericOptsS4 :: Bool
prop_checkGenericOptsS4 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-x" [String
"x"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGetOptsL1 :: Bool
prop_checkGetOptsL1 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"--foo=bar baz" [String
"foo"] [String
"baz"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
False) String
"" [(String
"foo", Bool
True)]
prop_checkGetOptsL2 :: Bool
prop_checkGetOptsL2 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"--foo bar baz" [String
"foo"] [String
"baz"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
False) String
"" [(String
"foo", Bool
True)]
prop_checkGetOptsL3 :: Bool
prop_checkGetOptsL3 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"--foo baz" [String
"foo"] [String
"baz"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"" []
prop_checkGetOptsL4 :: Bool
prop_checkGetOptsL4 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"--foo baz" [] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
False) String
"" []
prop_checkGenericOptsL1 :: Bool
prop_checkGenericOptsL1 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"--foo=bar" [String
"foo"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGenericOptsL2 :: Bool
prop_checkGenericOptsL2 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"--foo bar" [String
"foo"] [String
"bar"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGenericOptsL3 :: Bool
prop_checkGenericOptsL3 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-x --foo" [String
"x", String
"foo"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGetOptsT1 :: Bool
prop_checkGetOptsT1 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-a x -b" [String
"a", String
"b"] [String
"x"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"ab" []
prop_checkGetOptsT2 :: Bool
prop_checkGetOptsT2 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-a x -b" [String
"a"] [String
"x",String
"-b"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
False, Bool
True) String
"ab" []
prop_checkGetOptsT3 :: Bool
prop_checkGetOptsT3 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-a -- -b" [String
"a"] [String
"-b"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"ab" []
prop_checkGetOptsT4 :: Bool
prop_checkGetOptsT4 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-a -- -b" [String
"a", String
"b"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> String
-> [(String, Bool)]
-> [Token]
-> Maybe [(String, (Token, Token))]
getOpts (Bool
True, Bool
True) String
"a:b" []
prop_checkGenericOptsT1 :: Bool
prop_checkGenericOptsT1 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-x -- -y" [String
"x"] [String
"-y"] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
prop_checkGenericOptsT2 :: Bool
prop_checkGenericOptsT2 = String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (Token, Token))])
-> Bool
forall a.
String
-> [String]
-> [String]
-> ([Token] -> Maybe [(String, (a, Token))])
-> Bool
checkGetOpts String
"-xy --" [String
"x", String
"y"] [] (([Token] -> Maybe [(String, (Token, Token))]) -> Bool)
-> ([Token] -> Maybe [(String, (Token, Token))]) -> Bool
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> ([Token] -> [(String, (Token, Token))])
-> [Token]
-> Maybe [(String, (Token, Token))]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [(String, (Token, Token))]
getGenericOpts
buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
buildCommandMap :: [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap = (Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis))
-> Map CommandName (Token -> Analysis)
-> [CommandCheck]
-> Map CommandName (Token -> Analysis)
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck Map CommandName (Token -> Analysis)
forall k a. Map k a
Map.empty
where
addCheck :: Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck Map CommandName (Token -> Analysis)
map (CommandCheck CommandName
name Token -> Analysis
function) =
((Token -> Analysis) -> (Token -> Analysis) -> Token -> Analysis)
-> CommandName
-> (Token -> Analysis)
-> Map CommandName (Token -> Analysis)
-> Map CommandName (Token -> Analysis)
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith (Token -> Analysis) -> (Token -> Analysis) -> Token -> Analysis
forall a. (a -> Analysis) -> (a -> Analysis) -> a -> Analysis
composeAnalyzers CommandName
name Token -> Analysis
function Map CommandName (Token -> Analysis)
map
checkCommand :: Map.Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand :: Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand Map CommandName (Token -> Analysis)
map t :: Token
t@(T_SimpleCommand Id
id [Token]
cmdPrefix (Token
cmd:[Token]
rest)) = Maybe Analysis -> Analysis
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe Analysis -> Analysis) -> Maybe Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
String
name <- Token -> Maybe String
getLiteralString Token
cmd
Analysis -> Maybe Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (Analysis -> Maybe Analysis) -> Analysis -> Maybe Analysis
forall a b. (a -> b) -> a -> b
$
if Char
'/' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
name
then
(Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Basename (String -> CommandName) -> String -> CommandName
forall a b. (a -> b) -> a -> b
$ String -> String
basename String
name) Map CommandName (Token -> Analysis)
map Token
t
else if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"builtin" Bool -> Bool -> Bool
&& Bool -> Bool
not ([Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
rest) then
let t' :: Token
t' = Id -> [Token] -> [Token] -> Token
T_SimpleCommand Id
id [Token]
cmdPrefix [Token]
rest
selectedBuiltin :: String
selectedBuiltin = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString (Token -> Maybe String)
-> ([Token] -> Token) -> [Token] -> Maybe String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> Token
forall a. [a] -> a
head ([Token] -> Maybe String) -> [Token] -> Maybe String
forall a b. (a -> b) -> a -> b
$ [Token]
rest
in (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Exactly String
selectedBuiltin) Map CommandName (Token -> Analysis)
map Token
t'
else do
(Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Exactly String
name) Map CommandName (Token -> Analysis)
map Token
t
(Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Basename String
name) Map CommandName (Token -> Analysis)
map Token
t
where
basename :: String -> String
basename = String -> String
forall a. [a] -> [a]
reverse (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'/') (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
forall a. [a] -> [a]
reverse
checkCommand Map CommandName (Token -> Analysis)
_ Token
_ = () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return ()
getChecker :: [CommandCheck] -> Checker
getChecker :: [CommandCheck] -> Checker
getChecker [CommandCheck]
list = Checker :: (Root -> Analysis) -> (Token -> Analysis) -> Checker
Checker {
perScript :: Root -> Analysis
perScript = Analysis -> Root -> Analysis
forall a b. a -> b -> a
const (Analysis -> Root -> Analysis) -> Analysis -> Root -> Analysis
forall a b. (a -> b) -> a -> b
$ () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (),
perToken :: Token -> Analysis
perToken = Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand Map CommandName (Token -> Analysis)
map
}
where
map :: Map CommandName (Token -> Analysis)
map = [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap [CommandCheck]
list
checker :: AnalysisSpec -> Parameters -> Checker
checker :: AnalysisSpec -> Parameters -> Checker
checker AnalysisSpec
spec Parameters
params = [CommandCheck] -> Checker
getChecker ([CommandCheck] -> Checker) -> [CommandCheck] -> Checker
forall a b. (a -> b) -> a -> b
$ [CommandCheck]
commandChecks [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ [CommandCheck]
optionals
where
keys :: [String]
keys = AnalysisSpec -> [String]
asOptionalChecks AnalysisSpec
spec
optionals :: [CommandCheck]
optionals =
if String
"all" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
keys
then ((CheckDescription, CommandCheck) -> CommandCheck)
-> [(CheckDescription, CommandCheck)] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map (CheckDescription, CommandCheck) -> CommandCheck
forall a b. (a, b) -> b
snd [(CheckDescription, CommandCheck)]
optionalCommandChecks
else (String -> Maybe CommandCheck) -> [String] -> [CommandCheck]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\String
x -> String -> Map String CommandCheck -> Maybe CommandCheck
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
x Map String CommandCheck
optionalCheckMap) [String]
keys
prop_checkTr1 :: Bool
prop_checkTr1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr String
"tr [a-f] [A-F]"
prop_checkTr2 :: Bool
prop_checkTr2 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr String
"tr 'a-z' 'A-Z'"
prop_checkTr2a :: Bool
prop_checkTr2a= CommandCheck -> String -> Bool
verify CommandCheck
checkTr String
"tr '[a-z]' '[A-Z]'"
prop_checkTr3 :: Bool
prop_checkTr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr -d '[:lower:]'"
prop_checkTr3a :: Bool
prop_checkTr3a= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr -d '[:upper:]'"
prop_checkTr3b :: Bool
prop_checkTr3b= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr -d '|/_[:upper:]'"
prop_checkTr4 :: Bool
prop_checkTr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"ls [a-z]"
prop_checkTr5 :: Bool
prop_checkTr5 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr String
"tr foo bar"
prop_checkTr6 :: Bool
prop_checkTr6 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr String
"tr 'hello' 'world'"
prop_checkTr8 :: Bool
prop_checkTr8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr aeiou _____"
prop_checkTr9 :: Bool
prop_checkTr9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"a-z n-za-m"
prop_checkTr10 :: Bool
prop_checkTr10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr --squeeze-repeats rl lr"
prop_checkTr11 :: Bool
prop_checkTr11= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr abc '[d*]'"
prop_checkTr12 :: Bool
prop_checkTr12= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr String
"tr '[=e=]' 'e'"
checkTr :: CommandCheck
checkTr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"tr") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
f :: Token -> m ()
f Token
w | Token -> Bool
isGlob Token
w =
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
w) Code
2060 String
"Quote parameters to tr to prevent glob expansion."
f Token
word =
case Token -> Maybe String
getLiteralString Token
word of
Just String
"a-z" -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) Code
2018 String
"Use '[:lower:]' to support accents and foreign alphabets."
Just String
"A-Z" -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) Code
2019 String
"Use '[:upper:]' to support accents and foreign alphabets."
Just String
s -> do
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
|| String
"[:" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s) Bool -> Bool -> Bool
&& String -> Bool
duplicated String
s) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) Code
2020 String
"tr replaces sets of chars, not words (mentioned due to duplicates)."
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
"[:" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
|| String
"[=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"[" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
&& String
"]" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
s Bool -> Bool -> Bool
&& (String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
2) Bool -> Bool -> Bool
&& (Char
'*' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
s)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) Code
2021 String
"Don't use [] around classes in tr, it replaces literal square brackets."
Maybe String
Nothing -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
duplicated :: String -> Bool
duplicated String
s =
let relevant :: String
relevant = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter Char -> Bool
isAlpha String
s
in String
relevant String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> String
forall a. Eq a => [a] -> [a]
nub String
relevant
prop_checkFindNameGlob1 :: Bool
prop_checkFindNameGlob1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindNameGlob String
"find / -name *.php"
prop_checkFindNameGlob2 :: Bool
prop_checkFindNameGlob2 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindNameGlob String
"find / -type f -ipath *(foo)"
prop_checkFindNameGlob3 :: Bool
prop_checkFindNameGlob3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindNameGlob String
"find * -name '*.php'"
checkFindNameGlob :: CommandCheck
checkFindNameGlob = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"find") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
acceptsGlob :: String -> Bool
acceptsGlob String
s = String
s String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ String
"-ilname", String
"-iname", String
"-ipath", String
"-iregex", String
"-iwholename", String
"-lname", String
"-name", String
"-path", String
"-regex", String
"-wholename" ]
f :: [Token] -> m ()
f [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
f (Token
x:[Token]
xs) = (Token -> (Token -> m ()) -> Token -> m ())
-> (Token -> m ()) -> [Token] -> Token -> m ()
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Token -> (Token -> m ()) -> Token -> m ()
forall (m :: * -> *) b.
MonadWriter [TokenComment] m =>
Token -> (Token -> m b) -> Token -> m b
g (m () -> Token -> m ()
forall a b. a -> b -> a
const (m () -> Token -> m ()) -> m () -> Token -> m ()
forall a b. (a -> b) -> a -> b
$ () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) [Token]
xs Token
x
g :: Token -> (Token -> m b) -> Token -> m b
g Token
b Token -> m b
acc Token
a = do
Maybe String -> (String -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Token -> Maybe String
getLiteralString Token
a) ((String -> m ()) -> m ()) -> (String -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \String
s -> Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
acceptsGlob String
s Bool -> Bool -> Bool
&& Token -> Bool
isGlob Token
b) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
b) Code
2061 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Quote the parameter to " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" so the shell won't interpret it."
Token -> m b
acc Token
b
prop_checkExpr :: Bool
prop_checkExpr = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"foo=$(expr 3 + 2)"
prop_checkExpr2 :: Bool
prop_checkExpr2 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"foo=`echo \\`expr 3 + 2\\``"
prop_checkExpr3 :: Bool
prop_checkExpr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExpr String
"foo=$(expr foo : regex)"
prop_checkExpr4 :: Bool
prop_checkExpr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExpr String
"foo=$(expr foo \\< regex)"
prop_checkExpr5 :: Bool
prop_checkExpr5 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr match foo bar"
prop_checkExpr6 :: Bool
prop_checkExpr6 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr foo : fo*"
prop_checkExpr7 :: Bool
prop_checkExpr7 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr 5 -3"
prop_checkExpr8 :: Bool
prop_checkExpr8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr \"$@\""
prop_checkExpr9 :: Bool
prop_checkExpr9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr 5 $rest"
prop_checkExpr10 :: Bool
prop_checkExpr10 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr length \"$var\""
prop_checkExpr11 :: Bool
prop_checkExpr11 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr foo > bar"
prop_checkExpr12 :: Bool
prop_checkExpr12 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr 1 | 2"
prop_checkExpr13 :: Bool
prop_checkExpr13 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr 1 * 2"
prop_checkExpr14 :: Bool
prop_checkExpr14 = CommandCheck -> String -> Bool
verify CommandCheck
checkExpr String
"# shellcheck disable=SC2003\nexpr \"$x\" >= \"$y\""
checkExpr :: CommandCheck
checkExpr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"expr") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f where
f :: Token -> m ()
f Token
t = do
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
exceptions) ([Token] -> [String]
words ([Token] -> [String]) -> [Token] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2003
String
"expr is antiquated. Consider rewriting this using $((..)), ${} or [[ ]]."
case Token -> [Token]
arguments Token
t of
[Token
lhs, Token
op, Token
rhs] -> do
Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkOp Token
lhs
case Token -> [Token]
getWordParts Token
op of
[T_Glob Id
_ String
"*"] ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
op) Code
2304
String
"* must be escaped to multiply: \\*. Modern $((x * y)) avoids this issue."
[T_Literal Id
_ String
":"] | Token -> Bool
isGlob Token
rhs ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
rhs) Code
2305
String
"Quote regex argument to expr to avoid it expanding as a glob."
[Token]
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
[Token
single] | Bool -> Bool
not (Token -> Bool
willSplit Token
single) ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
single) Code
2307
String
"'expr' expects 3+ arguments but sees 1. Make sure each operator/operand is a separate argument, and escape <>&|."
[Token
first, Token
second] |
(String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
first) String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"length"
Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
willSplit Token
first Bool -> Bool -> Bool
|| Token -> Bool
willSplit Token
second) -> do
Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkOp Token
first
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2307
String
"'expr' expects 3+ arguments, but sees 2. Make sure each operator/operand is a separate argument, and escape <>&|."
(Token
first:[Token]
rest) -> do
Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkOp Token
first
[Token] -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
rest ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \Token
t ->
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
t) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2306 String
"Escape glob characters in arguments to expr to avoid pathname expansion."
[Token]
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
exceptions :: [String]
exceptions = [ String
":", String
"<", String
">", String
"<=", String
">=",
String
"match", String
"length", String
"substr", String
"index"]
words :: [Token] -> [String]
words = (Token -> Maybe String) -> [Token] -> [String]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe String
getLiteralString
checkOp :: Token -> m ()
checkOp Token
side =
case Token -> Maybe String
getLiteralString Token
side of
Just String
"match" -> String -> m ()
msg String
"'expr match' has unspecified results. Prefer 'expr str : regex'."
Just String
"length" -> String -> m ()
msg String
"'expr length' has unspecified results. Prefer ${#var}."
Just String
"substr" -> String -> m ()
msg String
"'expr substr' has unspecified results. Prefer 'cut' or ${var#???}."
Just String
"index" -> String -> m ()
msg String
"'expr index' has unspecified results. Prefer x=${var%%[chars]*}; $((${#x}+1))."
Maybe String
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
where
msg :: String -> m ()
msg = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
side) Code
2308
prop_checkGrepRe1 :: Bool
prop_checkGrepRe1 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe String
"cat foo | grep *.mp3"
prop_checkGrepRe2 :: Bool
prop_checkGrepRe2 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe String
"grep -Ev cow*test *.mp3"
prop_checkGrepRe3 :: Bool
prop_checkGrepRe3 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe String
"grep --regex=*.mp3 file"
prop_checkGrepRe4 :: Bool
prop_checkGrepRe4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep foo *.mp3"
prop_checkGrepRe5 :: Bool
prop_checkGrepRe5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep-v --regex=moo *"
prop_checkGrepRe6 :: Bool
prop_checkGrepRe6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep foo \\*.mp3"
prop_checkGrepRe7 :: Bool
prop_checkGrepRe7 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe String
"grep *foo* file"
prop_checkGrepRe8 :: Bool
prop_checkGrepRe8 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe String
"ls | grep foo*.jpg"
prop_checkGrepRe9 :: Bool
prop_checkGrepRe9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep '[0-9]*' file"
prop_checkGrepRe10 :: Bool
prop_checkGrepRe10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep '^aa*' file"
prop_checkGrepRe11 :: Bool
prop_checkGrepRe11= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --include=*.png foo"
prop_checkGrepRe12 :: Bool
prop_checkGrepRe12= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep -F 'Foo*' file"
prop_checkGrepRe13 :: Bool
prop_checkGrepRe13= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep -- -foo bar*"
prop_checkGrepRe14 :: Bool
prop_checkGrepRe14= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep -e -foo bar*"
prop_checkGrepRe15 :: Bool
prop_checkGrepRe15= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --regex -foo bar*"
prop_checkGrepRe16 :: Bool
prop_checkGrepRe16= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --include 'Foo*' file"
prop_checkGrepRe17 :: Bool
prop_checkGrepRe17= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --exclude 'Foo*' file"
prop_checkGrepRe18 :: Bool
prop_checkGrepRe18= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --exclude-dir 'Foo*' file"
prop_checkGrepRe19 :: Bool
prop_checkGrepRe19= CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe String
"grep -- 'Foo*' file"
prop_checkGrepRe20 :: Bool
prop_checkGrepRe20= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --fixed-strings 'Foo*' file"
prop_checkGrepRe21 :: Bool
prop_checkGrepRe21= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep -o 'x*' file"
prop_checkGrepRe22 :: Bool
prop_checkGrepRe22= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep --only-matching 'x*' file"
prop_checkGrepRe23 :: Bool
prop_checkGrepRe23= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe String
"grep '.*' file"
checkGrepRe :: CommandCheck
checkGrepRe = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"grep") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check where
check :: Token -> m ()
check Token
cmd = Token -> [Token] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
f Token
cmd (Token -> [Token]
arguments Token
cmd)
skippable :: String -> Bool
skippable String
s = Bool -> Bool
not (String
"--regex=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s) Bool -> Bool -> Bool
&& String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s
f :: Token -> [Token] -> m ()
f Token
_ [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
f Token
cmd (Token
x:[Token]
r) =
let str :: String
str = String -> Token -> String
getLiteralStringDef String
"_" Token
x
in
if String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"--", String
"-e", String
"--regex"]
then Token -> [Token] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd [Token]
r
else
if String -> Bool
skippable String
str
then Token -> [Token] -> m ()
f Token
cmd [Token]
r
else Token -> [Token] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd (Token
xToken -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token]
r)
checkRE :: Token -> [Token] -> m ()
checkRE Token
_ [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkRE Token
cmd (Token
re:[Token]
_) = do
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
re) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
re) Code
2062 String
"Quote the grep pattern so the shell won't interpret it."
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String]
grepGlobFlags) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
let string :: String
string = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
re
if String -> Bool
isConfusedGlobRegex String
string then
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
re) Code
2063 String
"Grep uses regex, but this looks like a glob."
else Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
Char
char <- String -> Maybe Char
getSuspiciousRegexWildcard String
string
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
re) Code
2022 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
String
"Note that unlike globs, " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
char] String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"* here matches '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
char, Char
char, Char
char] String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' but not '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Char -> String
wordStartingWith Char
char String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"'."
where
flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
cmd
grepGlobFlags :: [String]
grepGlobFlags = [String
"fixed-strings", String
"F", String
"include", String
"exclude", String
"exclude-dir", String
"o", String
"only-matching"]
wordStartingWith :: Char -> String
wordStartingWith Char
c =
String -> [String] -> String
forall p. p -> [p] -> p
headOrDefault (Char
cChar -> String -> String
forall a. a -> [a] -> [a]
:String
"test") ([String] -> String)
-> ([String] -> [String]) -> [String] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter ([Char
c] String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ [String]
candidates
where
candidates :: [String]
candidates =
[String]
sampleWords [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ (String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (\(Char
x:String
r) -> Char -> Char
toUpper Char
x Char -> String -> String
forall a. a -> [a] -> [a]
: String
r) [String]
sampleWords
getSuspiciousRegexWildcard :: String -> Maybe Char
getSuspiciousRegexWildcard String
str = case Regex -> String -> Maybe [String]
matchRegex Regex
suspicious String
str of
Just [[Char
c]] | Bool -> Bool
not (String
str String -> Regex -> Bool
`matches` Regex
contra) -> Char -> Maybe Char
forall a. a -> Maybe a
Just Char
c
Maybe [String]
_ -> String -> Maybe Char
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"looks good"
suspicious :: Regex
suspicious = String -> Regex
mkRegex String
"([A-Za-z1-9])\\*"
contra :: Regex
contra = String -> Regex
mkRegex String
"[^a-zA-Z1-9]\\*|[][^$+\\\\]"
prop_checkTrapQuotes1 :: Bool
prop_checkTrapQuotes1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTrapQuotes String
"trap \"echo $num\" INT"
prop_checkTrapQuotes1a :: Bool
prop_checkTrapQuotes1a= CommandCheck -> String -> Bool
verify CommandCheck
checkTrapQuotes String
"trap \"echo `ls`\" INT"
prop_checkTrapQuotes2 :: Bool
prop_checkTrapQuotes2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTrapQuotes String
"trap 'echo $num' INT"
prop_checkTrapQuotes3 :: Bool
prop_checkTrapQuotes3 = CommandCheck -> String -> Bool
verify CommandCheck
checkTrapQuotes String
"trap \"echo $((1+num))\" EXIT DEBUG"
checkTrapQuotes :: CommandCheck
checkTrapQuotes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"trap") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
f :: [Token] -> m ()
f (Token
x:[Token]
_) = Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkTrap Token
x
f [Token]
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkTrap :: Token -> m ()
checkTrap (T_NormalWord Id
_ [T_DoubleQuoted Id
_ [Token]
rs]) = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkExpansions [Token]
rs
checkTrap Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
warning :: Id -> m ()
warning Id
id = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2064 String
"Use single quotes, otherwise this expands now rather than when signalled."
checkExpansions :: Token -> m ()
checkExpansions (T_DollarExpansion Id
id [Token]
_) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
checkExpansions (T_Backticked Id
id [Token]
_) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
checkExpansions (T_DollarBraced Id
id Bool
_ Token
_) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
checkExpansions (T_DollarArithmetic Id
id Token
_) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
checkExpansions Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkReturn1 :: Bool
prop_checkReturn1 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn String
"return"
prop_checkReturn2 :: Bool
prop_checkReturn2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn String
"return 1"
prop_checkReturn3 :: Bool
prop_checkReturn3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn String
"return $var"
prop_checkReturn4 :: Bool
prop_checkReturn4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn String
"return $((a|b))"
prop_checkReturn5 :: Bool
prop_checkReturn5 = CommandCheck -> String -> Bool
verify CommandCheck
checkReturn String
"return -1"
prop_checkReturn6 :: Bool
prop_checkReturn6 = CommandCheck -> String -> Bool
verify CommandCheck
checkReturn String
"return 1000"
prop_checkReturn7 :: Bool
prop_checkReturn7 = CommandCheck -> String -> Bool
verify CommandCheck
checkReturn String
"return 'hello world'"
checkReturn :: CommandCheck
checkReturn = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"return") ((Id -> Analysis) -> (Id -> Analysis) -> Token -> Analysis
forall (f :: * -> *).
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
(\Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c Code
2151 String
"Only one integer 0-255 can be returned. Use stdout for other data.")
(\Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c Code
2152 String
"Can only return 0-255. Other data should be written to stdout."))
prop_checkExit1 :: Bool
prop_checkExit1 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit"
prop_checkExit2 :: Bool
prop_checkExit2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit 1"
prop_checkExit3 :: Bool
prop_checkExit3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit $var"
prop_checkExit4 :: Bool
prop_checkExit4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit $((a|b))"
prop_checkExit5 :: Bool
prop_checkExit5 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit String
"exit -1"
prop_checkExit6 :: Bool
prop_checkExit6 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit String
"exit 1000"
prop_checkExit7 :: Bool
prop_checkExit7 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit String
"exit 'hello world'"
checkExit :: CommandCheck
checkExit = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"exit") ((Id -> Analysis) -> (Id -> Analysis) -> Token -> Analysis
forall (f :: * -> *).
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
(\Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c Code
2241 String
"The exit status can only be one integer 0-255. Use stdout for other data.")
(\Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c Code
2242 String
"Can only exit with status 0-255. Other data should be written to stdout/stderr."))
returnOrExit :: (Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit Id -> f ()
multi Id -> f ()
invalid = ([Token] -> f ()
f ([Token] -> f ()) -> (Token -> [Token]) -> Token -> f ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
f :: [Token] -> f ()
f (Token
first:Token
second:[Token]
_) =
Id -> f ()
multi (Token -> Id
getId Token
first)
f [Token
value] =
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isInvalid (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> String
literal Token
value) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> f ()
invalid (Token -> Id
getId Token
value)
f [Token]
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
isInvalid :: String -> Bool
isInvalid String
s = String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
s Bool -> Bool -> Bool
|| (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isDigit) String
s Bool -> Bool -> Bool
|| String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
5
Bool -> Bool -> Bool
|| let value :: Code
value = (String -> Code
forall a. Read a => String -> a
read String
s :: Integer) in Code
value Code -> Code -> Bool
forall a. Ord a => a -> a -> Bool
> Code
255
literal :: Token -> String
literal Token
token = Identity String -> String
forall a. Identity a -> a
runIdentity (Identity String -> String) -> Identity String -> String
forall a b. (a -> b) -> a -> b
$ (Token -> Identity String) -> Token -> Identity String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt Token -> Identity String
forall (m :: * -> *). Monad m => Token -> m String
lit Token
token
lit :: Token -> m String
lit (T_DollarBraced {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"0"
lit (T_DollarArithmetic {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"0"
lit (T_DollarExpansion {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"0"
lit (T_Backticked {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"0"
lit Token
_ = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"WTF"
prop_checkFindExecWithSingleArgument1 :: Bool
prop_checkFindExecWithSingleArgument1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindExecWithSingleArgument String
"find . -exec 'cat {} | wc -l' \\;"
prop_checkFindExecWithSingleArgument2 :: Bool
prop_checkFindExecWithSingleArgument2 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindExecWithSingleArgument String
"find . -execdir 'cat {} | wc -l' +"
prop_checkFindExecWithSingleArgument3 :: Bool
prop_checkFindExecWithSingleArgument3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindExecWithSingleArgument String
"find . -exec wc -l {} \\;"
checkFindExecWithSingleArgument :: CommandCheck
checkFindExecWithSingleArgument = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"find") ([Token] -> Analysis
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
f :: [Token] -> Analysis
f = RWST Parameters [TokenComment] Cache Identity [()] -> Analysis
forall (f :: * -> *) a. Functor f => f a -> f ()
void (RWST Parameters [TokenComment] Cache Identity [()] -> Analysis)
-> ([Token] -> RWST Parameters [TokenComment] Cache Identity [()])
-> [Token]
-> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Analysis] -> RWST Parameters [TokenComment] Cache Identity [()]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([Analysis] -> RWST Parameters [TokenComment] Cache Identity [()])
-> ([Token] -> [Analysis])
-> [Token]
-> RWST Parameters [TokenComment] Cache Identity [()]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Token] -> Maybe Analysis) -> [[Token]] -> [Analysis]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe [Token] -> Maybe Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> Maybe (m ())
check ([[Token]] -> [Analysis])
-> ([Token] -> [[Token]]) -> [Token] -> [Analysis]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [[Token]]
forall a. [a] -> [[a]]
tails
check :: [Token] -> Maybe (m ())
check (Token
exec:Token
arg:Token
term:[Token]
_) = do
String
execS <- Token -> Maybe String
getLiteralString Token
exec
String
termS <- Token -> Maybe String
getLiteralString Token
term
let cmdS :: String
cmdS = String -> Token -> String
getLiteralStringDef String
" " Token
arg
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
execS String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-exec", String
"-execdir"] Bool -> Bool -> Bool
&& String
termS String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
";", String
"+"]
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
cmdS String -> Regex -> Bool
`matches` Regex
commandRegex
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
exec) Code
2150 String
"-exec does not invoke a shell. Rewrite or use -exec sh -c .. ."
check [Token]
_ = Maybe (m ())
forall a. Maybe a
Nothing
commandRegex :: Regex
commandRegex = String -> Regex
mkRegex String
"[ |;]"
prop_checkUnusedEchoEscapes1 :: Bool
prop_checkUnusedEchoEscapes1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnusedEchoEscapes String
"echo 'foo\\nbar\\n'"
prop_checkUnusedEchoEscapes2 :: Bool
prop_checkUnusedEchoEscapes2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes String
"echo -e 'foi\\nbar'"
prop_checkUnusedEchoEscapes3 :: Bool
prop_checkUnusedEchoEscapes3 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnusedEchoEscapes String
"echo \"n:\\t42\""
prop_checkUnusedEchoEscapes4 :: Bool
prop_checkUnusedEchoEscapes4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes String
"echo lol"
prop_checkUnusedEchoEscapes5 :: Bool
prop_checkUnusedEchoEscapes5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes String
"echo -n -e '\n'"
checkUnusedEchoEscapes :: CommandCheck
checkUnusedEchoEscapes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"echo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
hasEscapes :: Regex
hasEscapes = String -> Regex
mkRegex String
"\\\\[rnt]"
f :: Token -> m ()
f Token
cmd =
[Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Bash, Shell
Ksh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
cmd Token -> String -> Bool
`hasFlag` String
"e") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
(Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
examine ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd
examine :: Token -> f ()
examine Token
token = do
let str :: String
str = Token -> String
onlyLiteralString Token
token
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
str String -> Regex -> Bool
`matches` Regex
hasEscapes) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
token) Code
2028 String
"echo may not expand escape sequences. Use printf."
prop_checkInjectableFindSh1 :: Bool
prop_checkInjectableFindSh1 = CommandCheck -> String -> Bool
verify CommandCheck
checkInjectableFindSh String
"find . -exec sh -c 'echo {}' \\;"
prop_checkInjectableFindSh2 :: Bool
prop_checkInjectableFindSh2 = CommandCheck -> String -> Bool
verify CommandCheck
checkInjectableFindSh String
"find . -execdir bash -c 'rm \"{}\"' ';'"
prop_checkInjectableFindSh3 :: Bool
prop_checkInjectableFindSh3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkInjectableFindSh String
"find . -exec sh -c 'rm \"$@\"' _ {} \\;"
checkInjectableFindSh :: CommandCheck
checkInjectableFindSh = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"find") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
check :: [Token] -> m ()
check [Token]
args = do
let idStrings :: [(Id, String)]
idStrings = (Token -> (Id, String)) -> [Token] -> [(Id, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\Token
x -> (Token -> Id
getId Token
x, Token -> String
onlyLiteralString Token
x)) [Token]
args
[String -> Bool] -> [(Id, String)] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[String -> Bool] -> [(Id, String)] -> m ()
match [String -> Bool]
pattern [(Id, String)]
idStrings
match :: [String -> Bool] -> [(Id, String)] -> m ()
match [String -> Bool]
_ [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
match [] ((Id, String)
next:[(Id, String)]
_) = (Id, String) -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
(Id, String) -> f ()
action (Id, String)
next
match (String -> Bool
p:[String -> Bool]
tests) ((Id
id, String
arg):[(Id, String)]
args) = do
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
p String
arg) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ [String -> Bool] -> [(Id, String)] -> m ()
match [String -> Bool]
tests [(Id, String)]
args
[String -> Bool] -> [(Id, String)] -> m ()
match (String -> Bool
p(String -> Bool) -> [String -> Bool] -> [String -> Bool]
forall a. a -> [a] -> [a]
:[String -> Bool]
tests) [(Id, String)]
args
pattern :: [String -> Bool]
pattern = [
(String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-exec", String
"-execdir"]),
(String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"sh", String
"bash", String
"dash", String
"ksh"]),
(String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"-c")
]
action :: (Id, String) -> f ()
action (Id
id, String
arg) =
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"{}" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
arg) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2156 String
"Injecting filenames is fragile and insecure. Use parameters."
prop_checkFindActionPrecedence1 :: Bool
prop_checkFindActionPrecedence1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindActionPrecedence String
"find . -name '*.wav' -o -name '*.au' -exec rm {} +"
prop_checkFindActionPrecedence2 :: Bool
prop_checkFindActionPrecedence2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindActionPrecedence String
"find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)"
prop_checkFindActionPrecedence3 :: Bool
prop_checkFindActionPrecedence3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindActionPrecedence String
"find . -name '*.wav' -o -name '*.au'"
checkFindActionPrecedence :: CommandCheck
checkFindActionPrecedence = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"find") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
pattern :: [Token -> Bool]
pattern = [Token -> Bool
isMatch, Bool -> Token -> Bool
forall a b. a -> b -> a
const Bool
True, [String] -> Token -> Bool
forall (t :: * -> *). Foldable t => t String -> Token -> Bool
isParam [String
"-o", String
"-or"], Token -> Bool
isMatch, Bool -> Token -> Bool
forall a b. a -> b -> a
const Bool
True, Token -> Bool
isAction]
f :: [Token] -> m ()
f [Token]
list | [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< [Token -> Bool] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
f list :: [Token]
list@(Token
_:[Token]
rest) =
if [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
and (((Token -> Bool) -> Token -> Bool)
-> [Token -> Bool] -> [Token] -> [Bool]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (Token -> Bool) -> Token -> Bool
forall a b. (a -> b) -> a -> b
($) [Token -> Bool]
pattern [Token]
list)
then Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnFor ([Token]
list [Token] -> Int -> Token
forall a. [a] -> Int -> a
!! ([Token -> Bool] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1))
else [Token] -> m ()
f [Token]
rest
isMatch :: Token -> Bool
isMatch = [String] -> Token -> Bool
forall (t :: * -> *). Foldable t => t String -> Token -> Bool
isParam [ String
"-name", String
"-regex", String
"-iname", String
"-iregex", String
"-wholename", String
"-iwholename" ]
isAction :: Token -> Bool
isAction = [String] -> Token -> Bool
forall (t :: * -> *). Foldable t => t String -> Token -> Bool
isParam [ String
"-exec", String
"-execdir", String
"-delete", String
"-print", String
"-print0", String
"-fls", String
"-fprint", String
"-fprint0", String
"-fprintf", String
"-ls", String
"-ok", String
"-okdir", String
"-printf" ]
isParam :: t String -> Token -> Bool
isParam t String
strs Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
String
param <- Token -> Maybe String
getLiteralString Token
t
Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String
param String -> t String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t String
strs
warnFor :: Token -> m ()
warnFor Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2146 String
"This action ignores everything before the -o. Use \\( \\) to group."
prop_checkMkdirDashPM0 :: Bool
prop_checkMkdirDashPM0 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 a/b"
prop_checkMkdirDashPM1 :: Bool
prop_checkMkdirDashPM1 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -pm 0755 $dir"
prop_checkMkdirDashPM2 :: Bool
prop_checkMkdirDashPM2 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -vpm 0755 a/b"
prop_checkMkdirDashPM3 :: Bool
prop_checkMkdirDashPM3 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -pm 0755 -v a/b"
prop_checkMkdirDashPM4 :: Bool
prop_checkMkdirDashPM4 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir --parents --mode=0755 a/b"
prop_checkMkdirDashPM5 :: Bool
prop_checkMkdirDashPM5 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir --parents --mode 0755 a/b"
prop_checkMkdirDashPM6 :: Bool
prop_checkMkdirDashPM6 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -p --mode=0755 a/b"
prop_checkMkdirDashPM7 :: Bool
prop_checkMkdirDashPM7 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir --parents -m 0755 a/b"
prop_checkMkdirDashPM8 :: Bool
prop_checkMkdirDashPM8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p a/b"
prop_checkMkdirDashPM9 :: Bool
prop_checkMkdirDashPM9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -m 0755 a/b"
prop_checkMkdirDashPM10 :: Bool
prop_checkMkdirDashPM10 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir a/b"
prop_checkMkdirDashPM11 :: Bool
prop_checkMkdirDashPM11 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir --parents a/b"
prop_checkMkdirDashPM12 :: Bool
prop_checkMkdirDashPM12 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir --mode=0755 a/b"
prop_checkMkdirDashPM13 :: Bool
prop_checkMkdirDashPM13 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir_func -pm 0755 a/b"
prop_checkMkdirDashPM14 :: Bool
prop_checkMkdirDashPM14 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 singlelevel"
prop_checkMkdirDashPM15 :: Bool
prop_checkMkdirDashPM15 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 ../bin"
prop_checkMkdirDashPM16 :: Bool
prop_checkMkdirDashPM16 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 ../bin/laden"
prop_checkMkdirDashPM17 :: Bool
prop_checkMkdirDashPM17 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 ./bin"
prop_checkMkdirDashPM18 :: Bool
prop_checkMkdirDashPM18 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 ./bin/laden"
prop_checkMkdirDashPM19 :: Bool
prop_checkMkdirDashPM19 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 ./../bin"
prop_checkMkdirDashPM20 :: Bool
prop_checkMkdirDashPM20 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 .././bin"
prop_checkMkdirDashPM21 :: Bool
prop_checkMkdirDashPM21 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM String
"mkdir -p -m 0755 ../../bin"
checkMkdirDashPM :: CommandCheck
checkMkdirDashPM = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"mkdir") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
where
check :: Token -> m ()
check Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
let flags :: [(Token, String)]
flags = Token -> [(Token, String)]
getAllFlags Token
t
(Token, String)
dashP <- ((Token, String) -> Bool)
-> [(Token, String)] -> Maybe (Token, String)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(Token
_,String
f) -> String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"p" Bool -> Bool -> Bool
|| String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"parents") [(Token, String)]
flags
(Token, String)
dashM <- ((Token, String) -> Bool)
-> [(Token, String)] -> Maybe (Token, String)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(Token
_,String
f) -> String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"m" Bool -> Bool -> Bool
|| String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"mode") [(Token, String)]
flags
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
couldHaveSubdirs (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop Int
1 ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t)
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ (Token, String) -> Token
forall a b. (a, b) -> a
fst (Token, String)
dashM) Code
2174 String
"When used with -p, -m only applies to the deepest directory."
couldHaveSubdirs :: Token -> Bool
couldHaveSubdirs Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
True (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
String
name <- Token -> Maybe String
getLiteralString Token
t
Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Char
'/' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
name Bool -> Bool -> Bool
&& Bool -> Bool
not (String
name String -> Regex -> Bool
`matches` Regex
re)
re :: Regex
re = String -> Regex
mkRegex String
"^(\\.\\.?\\/)+[^/]+$"
prop_checkNonportableSignals1 :: Bool
prop_checkNonportableSignals1 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f 8"
prop_checkNonportableSignals2 :: Bool
prop_checkNonportableSignals2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals String
"trap f 0"
prop_checkNonportableSignals3 :: Bool
prop_checkNonportableSignals3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals String
"trap f 14"
prop_checkNonportableSignals4 :: Bool
prop_checkNonportableSignals4 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f SIGKILL"
prop_checkNonportableSignals5 :: Bool
prop_checkNonportableSignals5 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f 9"
prop_checkNonportableSignals6 :: Bool
prop_checkNonportableSignals6 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f stop"
prop_checkNonportableSignals7 :: Bool
prop_checkNonportableSignals7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals String
"trap 'stop' int"
checkNonportableSignals :: CommandCheck
checkNonportableSignals = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"trap") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
f :: [Token] -> m ()
f [Token]
args = case [Token]
args of
Token
first:[Token]
rest | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isFlag Token
first -> (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check [Token]
rest
[Token]
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
check :: Token -> m ()
check Token
param = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
str <- Token -> Maybe String
getLiteralString Token
param
let id :: Id
id = Token -> Id
getId Token
param
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ [m ()] -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ ([m ()] -> m ()) -> [m ()] -> m ()
forall a b. (a -> b) -> a -> b
$ ((Id -> String -> Maybe (m ())) -> Maybe (m ()))
-> [Id -> String -> Maybe (m ())] -> [m ()]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Id -> String -> Maybe (m ())
f -> Id -> String -> Maybe (m ())
f Id
id String
str) [
Id -> String -> Maybe (m ())
forall (m :: * -> *) (m :: * -> *).
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> String -> m (m ())
checkNumeric,
Id -> String -> Maybe (m ())
forall (m :: * -> *) (m :: * -> *).
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> String -> m (m ())
checkUntrappable
]
checkNumeric :: Id -> String -> m (m ())
checkNumeric Id
id String
str = do
Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
str)
Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
str
Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"0"
Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String
"1", String
"2", String
"3", String
"6", String
"9", String
"14", String
"15" ]
m () -> m (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> m (m ())) -> m () -> m (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2172
String
"Trapping signals by number is not well defined. Prefer signal names."
checkUntrappable :: Id -> String -> m (m ())
checkUntrappable Id
id String
str = do
Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"kill", String
"9", String
"sigkill", String
"stop", String
"sigstop"]
m () -> m (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> m (m ())) -> m () -> m (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2173
String
"SIGKILL/SIGSTOP can not be trapped."
prop_checkInteractiveSu1 :: Bool
prop_checkInteractiveSu1 = CommandCheck -> String -> Bool
verify CommandCheck
checkInteractiveSu String
"su; rm file; su $USER"
prop_checkInteractiveSu2 :: Bool
prop_checkInteractiveSu2 = CommandCheck -> String -> Bool
verify CommandCheck
checkInteractiveSu String
"su foo; something; exit"
prop_checkInteractiveSu3 :: Bool
prop_checkInteractiveSu3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkInteractiveSu String
"echo rm | su foo"
prop_checkInteractiveSu4 :: Bool
prop_checkInteractiveSu4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkInteractiveSu String
"su root < script"
checkInteractiveSu :: CommandCheck
checkInteractiveSu = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"su") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
f :: Token -> f ()
f Token
cmd = Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Token -> [Token]
arguments Token
cmd) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
1) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ do
[Token]
path <- Token -> f [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
cmd
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
undirected [Token]
path) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
cmd) Code
2117
String
"To run commands as another user, use su -c or sudo."
undirected :: Token -> Bool
undirected (T_Pipeline Id
_ [Token]
_ (Token
_:Token
_:[Token]
_)) = Bool
False
undirected (T_Redirecting Id
_ (Token
_:[Token]
_) Token
_) = Bool
False
undirected Token
_ = Bool
True
prop_checkSshCmdStr1 :: Bool
prop_checkSshCmdStr1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSshCommandString String
"ssh host \"echo $PS1\""
prop_checkSshCmdStr2 :: Bool
prop_checkSshCmdStr2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString String
"ssh host \"ls foo\""
prop_checkSshCmdStr3 :: Bool
prop_checkSshCmdStr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString String
"ssh \"$host\""
prop_checkSshCmdStr4 :: Bool
prop_checkSshCmdStr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString String
"ssh -i key \"$host\""
checkSshCommandString :: CommandCheck
checkSshCommandString = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"ssh") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
isOption :: Token -> Bool
isOption Token
x = String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
x)
f :: [Token] -> m ()
f [Token]
args =
case (Token -> Bool) -> [Token] -> ([Token], [Token])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Token -> Bool
isOption [Token]
args of
([], Token
hostport:r :: [Token]
r@(Token
_:[Token]
_)) -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg (Token -> m ()) -> Token -> m ()
forall a b. (a -> b) -> a -> b
$ [Token] -> Token
forall a. [a] -> a
last [Token]
r
([Token], [Token])
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkArg :: Token -> m ()
checkArg (T_NormalWord Id
_ [T_DoubleQuoted Id
id [Token]
parts]) =
Maybe Token -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ ((Token -> Bool) -> [Token] -> Maybe Token
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isConstant) [Token]
parts) ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$
\Token
x -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
x) Code
2029
String
"Note that, unescaped, this expands on the client side."
checkArg Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkPrintfVar1 :: Bool
prop_checkPrintfVar1 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf \"Lol: $s\""
prop_checkPrintfVar2 :: Bool
prop_checkPrintfVar2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf 'Lol: $s'"
prop_checkPrintfVar3 :: Bool
prop_checkPrintfVar3 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf -v cow $(cmd)"
prop_checkPrintfVar4 :: Bool
prop_checkPrintfVar4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf \"%${count}s\" var"
prop_checkPrintfVar5 :: Bool
prop_checkPrintfVar5 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf '%s %s %s' foo bar"
prop_checkPrintfVar6 :: Bool
prop_checkPrintfVar6 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf foo bar baz"
prop_checkPrintfVar7 :: Bool
prop_checkPrintfVar7 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf -- foo bar baz"
prop_checkPrintfVar8 :: Bool
prop_checkPrintfVar8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%s %s %s' \"${var[@]}\""
prop_checkPrintfVar9 :: Bool
prop_checkPrintfVar9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%s %s %s\\n' *.png"
prop_checkPrintfVar10 :: Bool
prop_checkPrintfVar10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%s %s %s' foo bar baz"
prop_checkPrintfVar11 :: Bool
prop_checkPrintfVar11= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%(%s%s)T' -1"
prop_checkPrintfVar12 :: Bool
prop_checkPrintfVar12= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf '%s %s\\n' 1 2 3"
prop_checkPrintfVar13 :: Bool
prop_checkPrintfVar13= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%s %s\\n' 1 2 3 4"
prop_checkPrintfVar14 :: Bool
prop_checkPrintfVar14= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf '%*s\\n' 1"
prop_checkPrintfVar15 :: Bool
prop_checkPrintfVar15= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%*s\\n' 1 2"
prop_checkPrintfVar16 :: Bool
prop_checkPrintfVar16= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf $'string'"
prop_checkPrintfVar17 :: Bool
prop_checkPrintfVar17= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf '%-*s\\n' 1"
prop_checkPrintfVar18 :: Bool
prop_checkPrintfVar18= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%-*s\\n' 1 2"
prop_checkPrintfVar19 :: Bool
prop_checkPrintfVar19= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%(%s)T'"
prop_checkPrintfVar20 :: Bool
prop_checkPrintfVar20= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar String
"printf '%d %(%s)T' 42"
prop_checkPrintfVar21 :: Bool
prop_checkPrintfVar21= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf '%d %(%s)T'"
prop_checkPrintfVar22 :: Bool
prop_checkPrintfVar22= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar String
"printf '%s\n%s' foo"
checkPrintfVar :: CommandCheck
checkPrintfVar = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"printf") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
f :: [Token] -> m ()
f (Token
doubledash:[Token]
rest) | Token -> Maybe String
getLiteralString Token
doubledash Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"--" = [Token] -> m ()
f [Token]
rest
f (Token
dashv:Token
var:[Token]
rest) | Token -> Maybe String
getLiteralString Token
dashv Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"-v" = [Token] -> m ()
f [Token]
rest
f (Token
format:[Token]
params) = Token -> [Token] -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadWriter [TokenComment] m, Foldable t) =>
Token -> t Token -> m ()
check Token
format [Token]
params
f [Token]
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
check :: Token -> t Token -> m ()
check Token
format t Token
more = do
Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
string <- Token -> Maybe String
getLiteralString Token
format
let formats :: String
formats = String -> String
getPrintfFormats String
string
let formatCount :: Int
formatCount = String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
formats
let argCount :: Int
argCount = t Token -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Token
more
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ if
| Int
argCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
&& Int
formatCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 ->
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
| Int
formatCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
&& Int
argCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
format) Code
2182
String
"This printf format string has no variables. Other arguments are ignored."
| (Token -> Bool) -> t Token -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
mayBecomeMultipleArgs t Token
more ->
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
| Int
argCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
formatCount Bool -> Bool -> Bool
&& String -> Int -> Bool
onlyTrailingTs String
formats Int
argCount ->
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
| Int
argCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Int
argCount Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
formatCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 ->
() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
| Bool
otherwise ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
format) Code
2183 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
String
"This format string has " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
formatCount String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" variables, but is passed " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
argCount String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" arguments."
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Char
'%' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
format) Bool -> Bool -> Bool
|| Token -> Bool
isLiteral Token
format) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
format) Code
2059
String
"Don't use variables in the printf format string. Use printf '..%s..' \"$foo\"."
where
onlyTrailingTs :: String -> Int -> Bool
onlyTrailingTs String
format Int
argCount =
(Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'T') (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
argCount String
format
prop_checkGetPrintfFormats1 :: Bool
prop_checkGetPrintfFormats1 = String -> String
getPrintfFormats String
"%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"s"
prop_checkGetPrintfFormats2 :: Bool
prop_checkGetPrintfFormats2 = String -> String
getPrintfFormats String
"%0*s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*s"
prop_checkGetPrintfFormats3 :: Bool
prop_checkGetPrintfFormats3 = String -> String
getPrintfFormats String
"%(%s)T" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"T"
prop_checkGetPrintfFormats4 :: Bool
prop_checkGetPrintfFormats4 = String -> String
getPrintfFormats String
"%d%%%(%s)T" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"dT"
prop_checkGetPrintfFormats5 :: Bool
prop_checkGetPrintfFormats5 = String -> String
getPrintfFormats String
"%bPassed: %d, %bFailed: %d%b, Skipped: %d, %bErrored: %d%b\\n" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"bdbdbdbdb"
prop_checkGetPrintfFormats6 :: Bool
prop_checkGetPrintfFormats6 = String -> String
getPrintfFormats String
"%s%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"ss"
prop_checkGetPrintfFormats7 :: Bool
prop_checkGetPrintfFormats7 = String -> String
getPrintfFormats String
"%s\n%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"ss"
getPrintfFormats :: String -> String
getPrintfFormats = String -> String
getFormats
where
getFormats :: String -> String
getFormats :: String -> String
getFormats String
string =
case String
string of
Char
'%':Char
'%':String
rest -> String -> String
getFormats String
rest
Char
'%':Char
'(':String
rest ->
case (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
')') String
rest of
Char
')':Char
c:String
trailing -> Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String -> String
getFormats String
trailing
String
_ -> String
""
Char
'%':String
rest -> String -> String
regexBasedGetFormats String
rest
Char
_:String
rest -> String -> String
getFormats String
rest
[] -> String
""
regexBasedGetFormats :: String -> String
regexBasedGetFormats String
rest =
case Regex -> String -> Maybe [String]
matchRegex Regex
re String
rest of
Just [String
width, String
precision, String
typ, String
rest, String
_] ->
(if String
width String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*" then String
"*" else String
"") String -> String -> String
forall a. [a] -> [a] -> [a]
++
(if String
precision String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*" then String
"*" else String
"") String -> String -> String
forall a. [a] -> [a] -> [a]
++
String
typ String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
getFormats String
rest
Maybe [String]
Nothing -> Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
1 String
rest String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
getFormats String
rest
where
re :: Regex
re = String -> Regex
mkRegex String
"#?-?\\+? ?0?(\\*|\\d*)\\.?(\\d*|\\*)([diouxXfFeEgGaAcsbq])((\n|.)*)"
prop_checkUuoeCmd1 :: Bool
prop_checkUuoeCmd1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd String
"echo $(date)"
prop_checkUuoeCmd2 :: Bool
prop_checkUuoeCmd2 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd String
"echo `date`"
prop_checkUuoeCmd3 :: Bool
prop_checkUuoeCmd3 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd String
"echo \"$(date)\""
prop_checkUuoeCmd4 :: Bool
prop_checkUuoeCmd4 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd String
"echo \"`date`\""
prop_checkUuoeCmd5 :: Bool
prop_checkUuoeCmd5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUuoeCmd String
"echo \"The time is $(date)\""
prop_checkUuoeCmd6 :: Bool
prop_checkUuoeCmd6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUuoeCmd String
"echo \"$(<file)\""
checkUuoeCmd :: CommandCheck
checkUuoeCmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"echo") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
msg :: Id -> m ()
msg Id
id = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2005 String
"Useless echo? Instead of 'echo $(cmd)', just use 'cmd'."
f :: [Token] -> f ()
f [Token
token] = Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
tokenIsJustCommandOutput Token
token) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ Id -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
msg (Token -> Id
getId Token
token)
f [Token]
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkSetAssignment1 :: Bool
prop_checkSetAssignment1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSetAssignment String
"set foo 42"
prop_checkSetAssignment2 :: Bool
prop_checkSetAssignment2 = CommandCheck -> String -> Bool
verify CommandCheck
checkSetAssignment String
"set foo = 42"
prop_checkSetAssignment3 :: Bool
prop_checkSetAssignment3 = CommandCheck -> String -> Bool
verify CommandCheck
checkSetAssignment String
"set foo=42"
prop_checkSetAssignment4 :: Bool
prop_checkSetAssignment4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSetAssignment String
"set -- if=/dev/null"
prop_checkSetAssignment5 :: Bool
prop_checkSetAssignment5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSetAssignment String
"set 'a=5'"
prop_checkSetAssignment6 :: Bool
prop_checkSetAssignment6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSetAssignment String
"set"
checkSetAssignment :: CommandCheck
checkSetAssignment = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"set") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
f :: [Token] -> m ()
f (Token
var:[Token]
rest)
| (Bool -> Bool
not ([Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
rest) Bool -> Bool -> Bool
&& String -> Bool
isVariableName String
str) Bool -> Bool -> Bool
|| String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
isAssignment String
str =
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
var) Code
2121 String
"To assign a variable, use just 'var=value', no 'set ..'."
where str :: String
str = Token -> String
literal Token
var
f [Token]
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
isAssignment :: t Char -> Bool
isAssignment t Char
str = Char
'=' Char -> t Char -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t Char
str
literal :: Token -> String
literal (T_NormalWord Id
_ [Token]
l) = (Token -> String) -> [Token] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> String
literal [Token]
l
literal (T_Literal Id
_ String
str) = String
str
literal Token
_ = String
"*"
prop_checkExportedExpansions1 :: Bool
prop_checkExportedExpansions1 = CommandCheck -> String -> Bool
verify CommandCheck
checkExportedExpansions String
"export $foo"
prop_checkExportedExpansions2 :: Bool
prop_checkExportedExpansions2 = CommandCheck -> String -> Bool
verify CommandCheck
checkExportedExpansions String
"export \"$foo\""
prop_checkExportedExpansions3 :: Bool
prop_checkExportedExpansions3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExportedExpansions String
"export foo"
prop_checkExportedExpansions4 :: Bool
prop_checkExportedExpansions4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExportedExpansions String
"export ${foo?}"
checkExportedExpansions :: CommandCheck
checkExportedExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"export") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
check :: Token -> m ()
check Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
name <- Token -> Maybe String
getSingleUnmodifiedBracedString Token
t
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2163 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
String
"This does not export '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"'. Remove $/${} for that, or use ${var?} to quiet."
prop_checkReadExpansions1 :: Bool
prop_checkReadExpansions1 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions String
"read $var"
prop_checkReadExpansions2 :: Bool
prop_checkReadExpansions2 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions String
"read -r $var"
prop_checkReadExpansions3 :: Bool
prop_checkReadExpansions3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions String
"read -p $var"
prop_checkReadExpansions4 :: Bool
prop_checkReadExpansions4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions String
"read -rd $delim name"
prop_checkReadExpansions5 :: Bool
prop_checkReadExpansions5 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions String
"read \"$var\""
prop_checkReadExpansions6 :: Bool
prop_checkReadExpansions6 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions String
"read -a $var"
prop_checkReadExpansions7 :: Bool
prop_checkReadExpansions7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions String
"read $1"
prop_checkReadExpansions8 :: Bool
prop_checkReadExpansions8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions String
"read ${var?}"
prop_checkReadExpansions9 :: Bool
prop_checkReadExpansions9 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions String
"read arr[val]"
checkReadExpansions :: CommandCheck
checkReadExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"read") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
where
options :: [Token] -> Maybe [(String, (Token, Token))]
options = String -> [Token] -> Maybe [(String, (Token, Token))]
getGnuOpts String
flagsForRead
getVars :: Token -> [Token]
getVars Token
cmd = [Token] -> Maybe [Token] -> [Token]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [Token] -> [Token]) -> Maybe [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ do
[(String, (Token, Token))]
opts <- [Token] -> Maybe [(String, (Token, Token))]
options ([Token] -> Maybe [(String, (Token, Token))])
-> [Token] -> Maybe [(String, (Token, Token))]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd
[Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return [Token
y | (String
x,(Token
_, Token
y)) <- [(String, (Token, Token))]
opts, String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
x Bool -> Bool -> Bool
|| String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"a"]
check :: Token -> m ()
check Token
cmd = do
(Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
dollarWarning ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getVars Token
cmd
(Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
arrayWarning ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd
dollarWarning :: Token -> m ()
dollarWarning Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
name <- Token -> Maybe String
getSingleUnmodifiedBracedString Token
t
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2229 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
String
"This does not read '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"'. Remove $/${} for that, or use ${var?} to quiet."
arrayWarning :: Token -> f ()
arrayWarning Token
word =
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isUnquotedBracket ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
word) Code
2313 (String -> f ()) -> String -> f ()
forall a b. (a -> b) -> a -> b
$
String
"Quote array indices to avoid them expanding as globs."
isUnquotedBracket :: Token -> Bool
isUnquotedBracket Token
t =
case Token
t of
T_Glob Id
_ (Char
'[':String
_) -> Bool
True
Token
_ -> Bool
False
getSingleUnmodifiedBracedString :: Token -> Maybe String
getSingleUnmodifiedBracedString :: Token -> Maybe String
getSingleUnmodifiedBracedString Token
word =
case Token -> [Token]
getWordParts Token
word of
[T_DollarBraced Id
_ Bool
_ Token
l] ->
let contents :: String
contents = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
name :: String
name = String -> String
getBracedReference String
contents
in Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (String
contents String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name) Maybe () -> Maybe String -> Maybe String
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
contents
[Token]
_ -> Maybe String
forall a. Maybe a
Nothing
prop_checkAliasesUsesArgs1 :: Bool
prop_checkAliasesUsesArgs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkAliasesUsesArgs String
"alias a='cp $1 /a'"
prop_checkAliasesUsesArgs2 :: Bool
prop_checkAliasesUsesArgs2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkAliasesUsesArgs String
"alias $1='foo'"
prop_checkAliasesUsesArgs3 :: Bool
prop_checkAliasesUsesArgs3 = CommandCheck -> String -> Bool
verify CommandCheck
checkAliasesUsesArgs String
"alias a=\"echo \\${@}\""
checkAliasesUsesArgs :: CommandCheck
checkAliasesUsesArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"alias") ([Token] -> Analysis
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
re :: Regex
re = String -> Regex
mkRegex String
"\\$\\{?[0-9*@]"
f :: [Token] -> Analysis
f = (Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg
checkArg :: Token -> f ()
checkArg Token
arg =
let string :: String
string = String -> Token -> String
getLiteralStringDef String
"_" Token
arg in
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string Bool -> Bool -> Bool
&& String
string String -> Regex -> Bool
`matches` Regex
re) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
arg) Code
2142
String
"Aliases can't use positional parameters. Use a function."
prop_checkAliasesExpandEarly1 :: Bool
prop_checkAliasesExpandEarly1 = CommandCheck -> String -> Bool
verify CommandCheck
checkAliasesExpandEarly String
"alias foo=\"echo $PWD\""
prop_checkAliasesExpandEarly2 :: Bool
prop_checkAliasesExpandEarly2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkAliasesExpandEarly String
"alias -p"
prop_checkAliasesExpandEarly3 :: Bool
prop_checkAliasesExpandEarly3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkAliasesExpandEarly String
"alias foo='echo {1..10}'"
checkAliasesExpandEarly :: CommandCheck
checkAliasesExpandEarly = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"alias") ([Token] -> Analysis
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
f :: [Token] -> Analysis
f = (Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg
checkArg :: Token -> m ()
checkArg Token
arg | Char
'=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
arg) =
Maybe Token -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ ((Token -> Bool) -> [Token] -> Maybe Token
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isLiteral) ([Token] -> Maybe Token) -> [Token] -> Maybe Token
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
arg) ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$
\Token
x -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
x) Code
2139 String
"This expands when defined, not when used. Consider escaping."
checkArg Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkUnsetGlobs1 :: Bool
prop_checkUnsetGlobs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnsetGlobs String
"unset foo[1]"
prop_checkUnsetGlobs2 :: Bool
prop_checkUnsetGlobs2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnsetGlobs String
"unset foo"
prop_checkUnsetGlobs3 :: Bool
prop_checkUnsetGlobs3 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnsetGlobs String
"unset foo[$i]"
prop_checkUnsetGlobs4 :: Bool
prop_checkUnsetGlobs4 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnsetGlobs String
"unset foo[x${i}y]"
prop_checkUnsetGlobs5 :: Bool
prop_checkUnsetGlobs5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnsetGlobs String
"unset foo]["
checkUnsetGlobs :: CommandCheck
checkUnsetGlobs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"unset") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
check :: Token -> f ()
check Token
arg =
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
arg) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
arg) Code
2184 String
"Quote arguments to unset so they're not glob expanded."
prop_checkFindWithoutPath1 :: Bool
prop_checkFindWithoutPath1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindWithoutPath String
"find -type f"
prop_checkFindWithoutPath2 :: Bool
prop_checkFindWithoutPath2 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindWithoutPath String
"find"
prop_checkFindWithoutPath3 :: Bool
prop_checkFindWithoutPath3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath String
"find . -type f"
prop_checkFindWithoutPath4 :: Bool
prop_checkFindWithoutPath4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath String
"find -H -L \"$path\" -print"
prop_checkFindWithoutPath5 :: Bool
prop_checkFindWithoutPath5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath String
"find -O3 ."
prop_checkFindWithoutPath6 :: Bool
prop_checkFindWithoutPath6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath String
"find -D exec ."
prop_checkFindWithoutPath7 :: Bool
prop_checkFindWithoutPath7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath String
"find --help"
prop_checkFindWithoutPath8 :: Bool
prop_checkFindWithoutPath8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath String
"find -Hx . -print"
checkFindWithoutPath :: CommandCheck
checkFindWithoutPath = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"find") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> f ()
f t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
args)) =
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
t Token -> String -> Bool
`hasFlag` String
"help" Bool -> Bool -> Bool
|| [Token] -> Bool
hasPath [Token]
args) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
cmd) Code
2185 String
"Some finds don't have a default path. Specify '.' explicitly."
hasPath :: [Token] -> Bool
hasPath (Token
first:[Token]
rest) =
let flag :: String
flag = String -> Token -> String
getLiteralStringDef String
"___" Token
first in
Bool -> Bool
not (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
flag) Bool -> Bool -> Bool
|| String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
isLeadingFlag String
flag Bool -> Bool -> Bool
&& [Token] -> Bool
hasPath [Token]
rest
hasPath [] = Bool
False
isLeadingFlag :: t Char -> Bool
isLeadingFlag t Char
flag = t Char -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Char
flag Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
2 Bool -> Bool -> Bool
|| (Char -> Bool) -> t Char -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
leadingFlagChars) t Char
flag
leadingFlagChars :: String
leadingFlagChars=String
"-EHLPXdfsxO0123456789"
prop_checkTimeParameters1 :: Bool
prop_checkTimeParameters1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTimeParameters String
"time -f lol sleep 10"
prop_checkTimeParameters2 :: Bool
prop_checkTimeParameters2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimeParameters String
"time sleep 10"
prop_checkTimeParameters3 :: Bool
prop_checkTimeParameters3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimeParameters String
"time -p foo"
prop_checkTimeParameters4 :: Bool
prop_checkTimeParameters4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimeParameters String
"command time -f lol sleep 10"
checkTimeParameters :: CommandCheck
checkTimeParameters = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"time") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
f :: Token -> m ()
f (T_SimpleCommand Id
_ [Token]
_ (Token
cmd:Token
args:[Token]
_)) =
[Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash, Shell
Sh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
let s :: String
s = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
args in
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
&& String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"-p") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
cmd) Code
2023 String
"The shell may override 'time' as seen in man time(1). Use 'command time ..' for that one."
f Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkTimedCommand1 :: Bool
prop_checkTimedCommand1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTimedCommand String
"#!/bin/sh\ntime -p foo | bar"
prop_checkTimedCommand2 :: Bool
prop_checkTimedCommand2 = CommandCheck -> String -> Bool
verify CommandCheck
checkTimedCommand String
"#!/bin/dash\ntime ( foo; bar; )"
prop_checkTimedCommand3 :: Bool
prop_checkTimedCommand3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimedCommand String
"#!/bin/sh\ntime sleep 1"
checkTimedCommand :: CommandCheck
checkTimedCommand = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"time") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f where
f :: Token -> m ()
f (T_SimpleCommand Id
_ [Token]
_ (Token
c:args :: [Token]
args@(Token
_:[Token]
_))) =
[Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Dash] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
let cmd :: Token
cmd = [Token] -> Token
forall a. [a] -> a
last [Token]
args
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isPiped Token
cmd) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
c) Code
2176 String
"'time' is undefined for pipelines. time single stage or bash -c instead."
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Maybe Bool
forall (m :: * -> *). MonadFail m => Token -> m Bool
isSimple Token
cmd Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
cmd) Code
2177 String
"'time' is undefined for compound commands, time sh -c instead."
f Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
isPiped :: Token -> Bool
isPiped Token
cmd =
case Token
cmd of
T_Pipeline Id
_ [Token]
_ (Token
_:Token
_:[Token]
_) -> Bool
True
Token
_ -> Bool
False
getCommand :: Token -> m Token
getCommand Token
cmd =
case Token
cmd of
T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
_ Token
a : [Token]
_) -> Token -> m Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
a
Token
_ -> String -> m Token
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
""
isSimple :: Token -> m Bool
isSimple Token
cmd = do
Token
innerCommand <- Token -> m Token
forall (m :: * -> *). MonadFail m => Token -> m Token
getCommand Token
cmd
case Token
innerCommand of
T_SimpleCommand {} -> Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
Token
_ -> Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
prop_checkLocalScope1 :: Bool
prop_checkLocalScope1 = CommandCheck -> String -> Bool
verify CommandCheck
checkLocalScope String
"local foo=3"
prop_checkLocalScope2 :: Bool
prop_checkLocalScope2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkLocalScope String
"f() { local foo=3; }"
checkLocalScope :: CommandCheck
checkLocalScope = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"local") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ \Token
t ->
[Shell] -> Analysis -> Analysis
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash, Shell
Dash] (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
[Token]
path <- Token -> RWST Parameters [TokenComment] Cache Identity [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isFunctionLike [Token]
path) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2168 String
"'local' is only valid in functions."
prop_checkDeprecatedTempfile1 :: Bool
prop_checkDeprecatedTempfile1 = CommandCheck -> String -> Bool
verify CommandCheck
checkDeprecatedTempfile String
"var=$(tempfile)"
prop_checkDeprecatedTempfile2 :: Bool
prop_checkDeprecatedTempfile2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkDeprecatedTempfile String
"tempfile=$(mktemp)"
checkDeprecatedTempfile :: CommandCheck
checkDeprecatedTempfile = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"tempfile") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
\Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2186 String
"tempfile is deprecated. Use mktemp instead."
prop_checkDeprecatedEgrep :: Bool
prop_checkDeprecatedEgrep = CommandCheck -> String -> Bool
verify CommandCheck
checkDeprecatedEgrep String
"egrep '.+'"
checkDeprecatedEgrep :: CommandCheck
checkDeprecatedEgrep = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"egrep") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
\Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2196 String
"egrep is non-standard and deprecated. Use grep -E instead."
prop_checkDeprecatedFgrep :: Bool
prop_checkDeprecatedFgrep = CommandCheck -> String -> Bool
verify CommandCheck
checkDeprecatedFgrep String
"fgrep '*' files"
checkDeprecatedFgrep :: CommandCheck
checkDeprecatedFgrep = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"fgrep") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
\Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2197 String
"fgrep is non-standard and deprecated. Use grep -F instead."
prop_checkWhileGetoptsCase1 :: Bool
prop_checkWhileGetoptsCase1 = CommandCheck -> String -> Bool
verify CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case $x in a) foo;; esac; done"
prop_checkWhileGetoptsCase2 :: Bool
prop_checkWhileGetoptsCase2 = CommandCheck -> String -> Bool
verify CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done"
prop_checkWhileGetoptsCase3 :: Bool
prop_checkWhileGetoptsCase3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done"
prop_checkWhileGetoptsCase4 :: Bool
prop_checkWhileGetoptsCase4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done"
prop_checkWhileGetoptsCase5 :: Bool
prop_checkWhileGetoptsCase5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done"
prop_checkWhileGetoptsCase6 :: Bool
prop_checkWhileGetoptsCase6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case $y in a) foo;; esac; done"
prop_checkWhileGetoptsCase7 :: Bool
prop_checkWhileGetoptsCase7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case x$x in xa) foo;; xb) foo;; esac; done"
prop_checkWhileGetoptsCase8 :: Bool
prop_checkWhileGetoptsCase8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do x=a; case $x in a) foo;; esac; done"
checkWhileGetoptsCase :: CommandCheck
checkWhileGetoptsCase = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"getopts") Token -> Analysis
f
where
f :: Token -> Analysis
f :: Token -> Analysis
f t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:Token
arg1:Token
name:[Token]
_)) = do
[Token]
path <- Token -> RWST Parameters [TokenComment] Cache Identity [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
Parameters
params <- RWST Parameters [TokenComment] Cache Identity Parameters
forall r (m :: * -> *). MonadReader r m => m r
ask
Maybe Analysis -> Analysis
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe Analysis -> Analysis) -> Maybe Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
String
options <- Token -> Maybe String
getLiteralString Token
arg1
String
getoptsVar <- Token -> Maybe String
getLiteralString Token
name
(T_WhileExpression Id
_ [Token]
_ [Token]
body) <- (Token -> Maybe Bool) -> [Token] -> Maybe Token
forall a. (a -> Maybe Bool) -> [a] -> Maybe a
findFirst Token -> Maybe Bool
whileLoop [Token]
path
caseCmd :: Token
caseCmd@(T_CaseExpression Id
_ Token
var [(CaseType, [Token], [Token])]
_) <- (Token -> Maybe Token) -> [Token] -> [Token]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Token
findCase [Token]
body [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! Int
0
[T_DollarBraced Id
_ Bool
_ Token
bracedWord] <- [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Token] -> Maybe [Token]) -> [Token] -> Maybe [Token]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
var
[T_Literal Id
_ String
caseVar] <- [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Token] -> Maybe [Token]) -> [Token] -> Maybe [Token]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
bracedWord
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
caseVar String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
getoptsVar
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> String -> Bool
modifiesVariable Parameters
params (Id -> [Token] -> Token
T_BraceGroup (Int -> Id
Id Int
0) [Token]
body) String
getoptsVar
Analysis -> Maybe Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (Analysis -> Maybe Analysis) -> Analysis -> Maybe Analysis
forall a b. (a -> b) -> a -> b
$ Id -> [String] -> Token -> Analysis
check (Token -> Id
getId Token
arg1) ((Char -> String) -> String -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Char -> String -> String
forall a. a -> [a] -> [a]
:[]) (String -> [String]) -> String -> [String]
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
':') String
options) Token
caseCmd
f Token
_ = () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return ()
check :: Id -> [String] -> Token -> Analysis
check :: Id -> [String] -> Token -> Analysis
check Id
optId [String]
opts (T_CaseExpression Id
id Token
_ [(CaseType, [Token], [Token])]
list) = do
Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Maybe String
forall a. Maybe a
Nothing Maybe String -> Map (Maybe String) Token -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`Map.member` Map (Maybe String) Token
handledMap) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
(String -> Analysis) -> [String] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Id -> Id -> String -> Analysis
forall (m :: * -> *) p.
MonadWriter [TokenComment] m =>
p -> Id -> String -> m ()
warnUnhandled Id
optId Id
id) ([String] -> Analysis) -> [String] -> Analysis
forall a b. (a -> b) -> a -> b
$ [Maybe String] -> [String]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe String] -> [String]) -> [Maybe String] -> [String]
forall a b. (a -> b) -> a -> b
$ Map (Maybe String) () -> [Maybe String]
forall k a. Map k a -> [k]
Map.keys Map (Maybe String) ()
notHandled
Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((Maybe String -> Bool) -> [Maybe String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Maybe String -> Map (Maybe String) Token -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`Map.member` Map (Maybe String) Token
handledMap) [String -> Maybe String
forall a. a -> Maybe a
Just String
"*",String -> Maybe String
forall a. a -> Maybe a
Just String
"?"]) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2220 String
"Invalid flags are not handled. Add a *) case."
((Maybe String, Token) -> Analysis)
-> [(Maybe String, Token)] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Maybe String, Token) -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
(Maybe String, Token) -> m ()
warnRedundant ([(Maybe String, Token)] -> Analysis)
-> [(Maybe String, Token)] -> Analysis
forall a b. (a -> b) -> a -> b
$ Map (Maybe String) Token -> [(Maybe String, Token)]
forall k a. Map k a -> [(k, a)]
Map.toList Map (Maybe String) Token
notRequested
where
handledMap :: Map (Maybe String) Token
handledMap = [(Maybe String, Token)] -> Map (Maybe String) Token
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList (((CaseType, [Token], [Token]) -> [(Maybe String, Token)])
-> [(CaseType, [Token], [Token])] -> [(Maybe String, Token)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (CaseType, [Token], [Token]) -> [(Maybe String, Token)]
forall a c. (a, [Token], c) -> [(Maybe String, Token)]
getHandledStrings [(CaseType, [Token], [Token])]
list)
requestedMap :: Map (Maybe String) ()
requestedMap = [(Maybe String, ())] -> Map (Maybe String) ()
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(Maybe String, ())] -> Map (Maybe String) ())
-> [(Maybe String, ())] -> Map (Maybe String) ()
forall a b. (a -> b) -> a -> b
$ (String -> (Maybe String, ())) -> [String] -> [(Maybe String, ())]
forall a b. (a -> b) -> [a] -> [b]
map (\String
x -> (String -> Maybe String
forall a. a -> Maybe a
Just String
x, ())) [String]
opts
notHandled :: Map (Maybe String) ()
notHandled = Map (Maybe String) ()
-> Map (Maybe String) Token -> Map (Maybe String) ()
forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference Map (Maybe String) ()
requestedMap Map (Maybe String) Token
handledMap
notRequested :: Map (Maybe String) Token
notRequested = Map (Maybe String) Token
-> Map (Maybe String) () -> Map (Maybe String) Token
forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference Map (Maybe String) Token
handledMap Map (Maybe String) ()
requestedMap
warnUnhandled :: p -> Id -> String -> m ()
warnUnhandled p
optId Id
caseId String
str =
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
caseId Code
2213 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"getopts specified -" String -> String -> String
forall a. [a] -> [a] -> [a]
++ (String -> String
e4m String
str) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", but it's not handled by this 'case'."
warnRedundant :: (Maybe String, Token) -> m ()
warnRedundant (Just String
str, Token
expr)
| String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String
"*", String
":", String
"?"] =
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
expr) Code
2214 String
"This case is not specified by getopts."
warnRedundant (Maybe String, Token)
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
getHandledStrings :: (a, [Token], c) -> [(Maybe String, Token)]
getHandledStrings (a
_, [Token]
globs, c
_) =
(Token -> (Maybe String, Token))
-> [Token] -> [(Maybe String, Token)]
forall a b. (a -> b) -> [a] -> [b]
map (\Token
x -> (Token -> Maybe String
literal Token
x, Token
x)) [Token]
globs
literal :: Token -> Maybe String
literal :: Token -> Maybe String
literal Token
t = do
Token -> Maybe String
getLiteralString Token
t Maybe String -> Maybe String -> Maybe String
forall a. Semigroup a => a -> a -> a
<> Token -> Maybe String
fromGlob Token
t
fromGlob :: Token -> Maybe String
fromGlob Token
t =
case Token
t of
T_Glob Id
_ [Char
'[', Char
c, Char
']'] -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return [Char
c]
T_Glob Id
_ String
"*" -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"*"
T_Glob Id
_ String
"?" -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"?"
Token
_ -> Maybe String
forall a. Maybe a
Nothing
whileLoop :: Token -> Maybe Bool
whileLoop Token
t =
case Token
t of
T_WhileExpression {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
T_Script {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
Token
_ -> Maybe Bool
forall a. Maybe a
Nothing
findCase :: Token -> Maybe Token
findCase Token
t =
case Token
t of
T_Annotation Id
_ [Annotation]
_ Token
x -> Token -> Maybe Token
findCase Token
x
T_Pipeline Id
_ [Token]
_ [Token
x] -> Token -> Maybe Token
findCase Token
x
T_Redirecting Id
_ [Token]
_ x :: Token
x@(T_CaseExpression {}) -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
x
Token
_ -> Maybe Token
forall a. Maybe a
Nothing
prop_checkCatastrophicRm1 :: Bool
prop_checkCatastrophicRm1 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -r $1/$2"
prop_checkCatastrophicRm2 :: Bool
prop_checkCatastrophicRm2 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -r /home/$foo"
prop_checkCatastrophicRm3 :: Bool
prop_checkCatastrophicRm3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm String
"rm -r /home/${USER:?}/*"
prop_checkCatastrophicRm4 :: Bool
prop_checkCatastrophicRm4 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -fr /home/$(whoami)/*"
prop_checkCatastrophicRm5 :: Bool
prop_checkCatastrophicRm5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm String
"rm -r /home/${USER:-thing}/*"
prop_checkCatastrophicRm6 :: Bool
prop_checkCatastrophicRm6 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm --recursive /etc/*$config*"
prop_checkCatastrophicRm8 :: Bool
prop_checkCatastrophicRm8 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -rf /home"
prop_checkCatastrophicRm10 :: Bool
prop_checkCatastrophicRm10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm String
"rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}"
prop_checkCatastrophicRm11 :: Bool
prop_checkCatastrophicRm11= CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -r /{bin,sbin}/$exec"
prop_checkCatastrophicRm12 :: Bool
prop_checkCatastrophicRm12= CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -r /{{usr,},{bin,sbin}}/$exec"
prop_checkCatastrophicRm13 :: Bool
prop_checkCatastrophicRm13= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm String
"rm -r /{{a,b},{c,d}}/$exec"
prop_checkCatastrophicRmA :: Bool
prop_checkCatastrophicRmA = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -rf /usr /lib/nvidia-current/xorg/xorg"
prop_checkCatastrophicRmB :: Bool
prop_checkCatastrophicRmB = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm String
"rm -rf \"$STEAMROOT/\"*"
checkCatastrophicRm :: CommandCheck
checkCatastrophicRm = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"rm") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ \Token
t ->
Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isRecursive Token
t) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$
(Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkWord ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
braceExpand) ([Token] -> Analysis) -> [Token] -> Analysis
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
where
isRecursive :: Token -> Bool
isRecursive = ((Token, String) -> Bool) -> [(Token, String)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"r", String
"R", String
"recursive"]) (String -> Bool)
-> ((Token, String) -> String) -> (Token, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, String) -> String
forall a b. (a, b) -> b
snd) ([(Token, String)] -> Bool)
-> (Token -> [(Token, String)]) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags
checkWord :: Token -> f ()
checkWord Token
token =
case Token -> Maybe String
getLiteralString Token
token of
Just String
str ->
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> String
forall (t :: * -> *). Foldable t => t Char -> String
fixPath String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
importantPaths) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2114 String
"Warning: deletes a system directory."
Maybe String
Nothing ->
Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkWord' Token
token
checkWord' :: Token -> m ()
checkWord' Token
token = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
filename <- Token -> Maybe String
getPotentialPath Token
token
let path :: String
path = String -> String
forall (t :: * -> *). Foldable t => t Char -> String
fixPath String
filename
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> (m () -> m ()) -> m () -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
path String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
importantPaths) (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2115 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Use \"${var:?}\" to ensure this never expands to " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
path String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" ."
fixPath :: t Char -> String
fixPath t Char
filename =
let normalized :: String
normalized = Char -> String -> String
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> [a]
skipRepeating Char
'/' (String -> String) -> (t Char -> String) -> t Char -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> t Char -> String
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> [a]
skipRepeating Char
'*' (t Char -> String) -> t Char -> String
forall a b. (a -> b) -> a -> b
$ t Char
filename in
if String
normalized String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"/" then String
normalized else Char -> String -> String
forall a. Eq a => a -> [a] -> [a]
stripTrailing Char
'/' String
normalized
getPotentialPath :: Token -> Maybe String
getPotentialPath = (Token -> Maybe String) -> Token -> Maybe String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt Token -> Maybe String
f
where
f :: Token -> Maybe String
f (T_Glob Id
_ String
str) = String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
str
f (T_DollarBraced Id
_ Bool
_ Token
word) =
let var :: String
var = Token -> String
onlyLiteralString Token
word in
if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
var) [String
":?", String
":-", String
":="]
then Maybe String
forall a. Maybe a
Nothing
else String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
""
f Token
_ = String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
""
stripTrailing :: a -> [a] -> [a]
stripTrailing a
c = [a] -> [a]
forall a. [a] -> [a]
reverse ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c) ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> [a]
forall a. [a] -> [a]
reverse
skipRepeating :: a -> t a -> [a]
skipRepeating a
c = (a -> [a] -> [a]) -> [a] -> t a -> [a]
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr a -> [a] -> [a]
go []
where
go :: a -> [a] -> [a]
go a
a [a]
r = a
a a -> [a] -> [a]
forall a. a -> [a] -> [a]
: case [a]
r of a
b:[a]
rest | a
b a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c Bool -> Bool -> Bool
&& a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
b -> [a]
rest; [a]
_ -> [a]
r
paths :: [String]
paths = [
String
"", String
"/bin", String
"/etc", String
"/home", String
"/mnt", String
"/usr", String
"/usr/share", String
"/usr/local",
String
"/var", String
"/lib", String
"/dev", String
"/media", String
"/boot", String
"/lib64", String
"/usr/bin"
]
importantPaths :: [String]
importantPaths = (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null) ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$
[String
"", String
"/", String
"/*", String
"/*/*"] [String] -> (String -> [String]) -> [String]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\String
x -> (String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> String -> String
forall a. [a] -> [a] -> [a]
++String
x) [String]
paths)
prop_checkLetUsage1 :: Bool
prop_checkLetUsage1 = CommandCheck -> String -> Bool
verify CommandCheck
checkLetUsage String
"let a=1"
prop_checkLetUsage2 :: Bool
prop_checkLetUsage2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkLetUsage String
"(( a=1 ))"
checkLetUsage :: CommandCheck
checkLetUsage = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"let") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
f :: Token -> m ()
f Token
t = [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash,Shell
Ksh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2219 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Instead of 'let expr', prefer (( expr )) ."
missingDestination :: (Token -> f ()) -> Token -> f ()
missingDestination Token -> f ()
handler Token
token = do
case [Token]
params of
[Token
single] -> do
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Bool
hasTarget Bool -> Bool -> Bool
|| Token -> Bool
mayBecomeMultipleArgs Token
single) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
Token -> f ()
handler Token
token
[Token]
_ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
where
args :: [(Token, String)]
args = Token -> [(Token, String)]
getAllFlags Token
token
params :: [Token]
params = [Token
x | (Token
x,String
"") <- [(Token, String)]
args]
hasTarget :: Bool
hasTarget =
((Token, String) -> Bool) -> [(Token, String)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\(Token
_,String
x) -> String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"" Bool -> Bool -> Bool
&& String
x String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
"target-directory") [(Token, String)]
args
prop_checkMvArguments1 :: Bool
prop_checkMvArguments1 = CommandCheck -> String -> Bool
verify CommandCheck
checkMvArguments String
"mv 'foo bar'"
prop_checkMvArguments2 :: Bool
prop_checkMvArguments2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv foo bar"
prop_checkMvArguments3 :: Bool
prop_checkMvArguments3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv 'foo bar'{,bak}"
prop_checkMvArguments4 :: Bool
prop_checkMvArguments4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv \"$@\""
prop_checkMvArguments5 :: Bool
prop_checkMvArguments5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv -t foo bar"
prop_checkMvArguments6 :: Bool
prop_checkMvArguments6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv --target-directory=foo bar"
prop_checkMvArguments7 :: Bool
prop_checkMvArguments7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv --target-direc=foo bar"
prop_checkMvArguments8 :: Bool
prop_checkMvArguments8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv --version"
prop_checkMvArguments9 :: Bool
prop_checkMvArguments9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments String
"mv \"${!var}\""
checkMvArguments :: CommandCheck
checkMvArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"mv") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ (Token -> Analysis) -> Token -> Analysis
forall (f :: * -> *). Monad f => (Token -> f ()) -> Token -> f ()
missingDestination Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> m ()
f Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2224 String
"This mv has no destination. Check the arguments."
checkCpArguments :: CommandCheck
checkCpArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"cp") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ (Token -> Analysis) -> Token -> Analysis
forall (f :: * -> *). Monad f => (Token -> f ()) -> Token -> f ()
missingDestination Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> m ()
f Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2225 String
"This cp has no destination. Check the arguments."
checkLnArguments :: CommandCheck
checkLnArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"ln") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ (Token -> Analysis) -> Token -> Analysis
forall (f :: * -> *). Monad f => (Token -> f ()) -> Token -> f ()
missingDestination Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> m ()
f Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2226 String
"This ln has no destination. Check the arguments, or specify '.' explicitly."
prop_checkFindRedirections1 :: Bool
prop_checkFindRedirections1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindRedirections String
"find . -exec echo {} > file \\;"
prop_checkFindRedirections2 :: Bool
prop_checkFindRedirections2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindRedirections String
"find . -exec echo {} \\; > file"
prop_checkFindRedirections3 :: Bool
prop_checkFindRedirections3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindRedirections String
"find . -execdir sh -c 'foo > file' \\;"
checkFindRedirections :: CommandCheck
checkFindRedirections = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"find") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
f :: Token -> m ()
f Token
t = do
Maybe Token
redirecting <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
case Maybe Token
redirecting of
Just (T_Redirecting Id
_ redirs :: [Token]
redirs@(Token
_:[Token]
_) (T_SimpleCommand Id
_ [Token]
_ args :: [Token]
args@(Token
_:Token
_:[Token]
_))) -> do
let minRedir :: Id
minRedir = [Id] -> Id
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum ([Id] -> Id) -> [Id] -> Id
forall a b. (a -> b) -> a -> b
$ (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
redirs
let maxArg :: Id
maxArg = [Id] -> Id
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Id] -> Id) -> [Id] -> Id
forall a b. (a -> b) -> a -> b
$ (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
args
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Id
minRedir Id -> Id -> Bool
forall a. Ord a => a -> a -> Bool
< Id
maxArg) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
minRedir Code
2227
String
"Redirection applies to the find command itself. Rewrite to work per action (or move to end)."
Maybe Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkWhich :: Bool
prop_checkWhich = CommandCheck -> String -> Bool
verify CommandCheck
checkWhich String
"which '.+'"
checkWhich :: CommandCheck
checkWhich = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"which") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
\Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2230 String
"'which' is non-standard. Use builtin 'command -v' instead."
prop_checkSudoRedirect1 :: Bool
prop_checkSudoRedirect1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect String
"sudo echo 3 > /proc/file"
prop_checkSudoRedirect2 :: Bool
prop_checkSudoRedirect2 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect String
"sudo cmd < input"
prop_checkSudoRedirect3 :: Bool
prop_checkSudoRedirect3 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect String
"sudo cmd >> file"
prop_checkSudoRedirect4 :: Bool
prop_checkSudoRedirect4 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect String
"sudo cmd &> file"
prop_checkSudoRedirect5 :: Bool
prop_checkSudoRedirect5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoRedirect String
"sudo cmd 2>&1"
prop_checkSudoRedirect6 :: Bool
prop_checkSudoRedirect6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoRedirect String
"sudo cmd 2> log"
prop_checkSudoRedirect7 :: Bool
prop_checkSudoRedirect7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoRedirect String
"sudo cmd > /dev/null 2>&1"
checkSudoRedirect :: CommandCheck
checkSudoRedirect = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"sudo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
f :: Token -> m ()
f Token
t = do
Maybe Token
t_redir <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
case Maybe Token
t_redir of
Just (T_Redirecting Id
_ [Token]
redirs Token
_) ->
(Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnAbout [Token]
redirs
warnAbout :: Token -> m ()
warnAbout (T_FdRedirect Id
_ String
s (T_IoFile Id
id Token
op Token
file))
| (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
s Bool -> Bool -> Bool
|| String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"&") Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
special Token
file) =
case Token
op of
T_Less Id
_ ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
op) Code
2024
String
"sudo doesn't affect redirects. Use sudo cat file | .."
T_Greater Id
_ ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
op) Code
2024
String
"sudo doesn't affect redirects. Use ..| sudo tee file"
T_DGREAT Id
_ ->
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
op) Code
2024
String
"sudo doesn't affect redirects. Use .. | sudo tee -a file"
Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
warnAbout Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
special :: Token -> Bool
special Token
file = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
file) String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"/dev/null"
prop_checkSudoArgs1 :: Bool
prop_checkSudoArgs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoArgs String
"sudo cd /root"
prop_checkSudoArgs2 :: Bool
prop_checkSudoArgs2 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoArgs String
"sudo export x=3"
prop_checkSudoArgs3 :: Bool
prop_checkSudoArgs3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs String
"sudo ls /usr/local/protected"
prop_checkSudoArgs4 :: Bool
prop_checkSudoArgs4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs String
"sudo ls && export x=3"
prop_checkSudoArgs5 :: Bool
prop_checkSudoArgs5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs String
"sudo echo ls"
prop_checkSudoArgs6 :: Bool
prop_checkSudoArgs6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs String
"sudo -n -u export ls"
prop_checkSudoArgs7 :: Bool
prop_checkSudoArgs7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs String
"sudo docker export foo"
checkSudoArgs :: CommandCheck
checkSudoArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"sudo") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> m ()
f Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
[(String, (Token, Token))]
opts <- [Token] -> Maybe [(String, (Token, Token))]
parseOpts ([Token] -> Maybe [(String, (Token, Token))])
-> [Token] -> Maybe [(String, (Token, Token))]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
let nonFlags :: [Token]
nonFlags = [Token
x | (String
"",(Token
x, Token
_)) <- [(String, (Token, Token))]
opts]
Token
commandArg <- [Token]
nonFlags [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! Int
0
String
command <- Token -> Maybe String
getLiteralString Token
commandArg
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
command String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
builtins
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2232 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Can't use sudo with builtins like " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
command String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
". Did you want sudo sh -c .. instead?"
builtins :: [String]
builtins = [ String
"cd", String
"eval", String
"export", String
"history", String
"read", String
"source", String
"wait" ]
parseOpts :: [Token] -> Maybe [(String, (Token, Token))]
parseOpts = String -> [Token] -> Maybe [(String, (Token, Token))]
getBsdOpts String
"vAknSbEHPa:g:h:p:u:c:T:r:"
prop_checkSourceArgs1 :: Bool
prop_checkSourceArgs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSourceArgs String
"#!/bin/sh\n. script arg"
prop_checkSourceArgs2 :: Bool
prop_checkSourceArgs2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSourceArgs String
"#!/bin/sh\n. script"
prop_checkSourceArgs3 :: Bool
prop_checkSourceArgs3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSourceArgs String
"#!/bin/bash\n. script arg"
checkSourceArgs :: CommandCheck
checkSourceArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
".") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
where
f :: Token -> m ()
f Token
t = [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Dash] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
case Token -> [Token]
arguments Token
t of
(Token
file:Token
arg1:[Token]
_) -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
arg1) Code
2240 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
String
"The dot command does not support arguments in sh/dash. Set them as variables."
[Token]
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
prop_checkChmodDashr1 :: Bool
prop_checkChmodDashr1 = CommandCheck -> String -> Bool
verify CommandCheck
checkChmodDashr String
"chmod -r 0755 dir"
prop_checkChmodDashr2 :: Bool
prop_checkChmodDashr2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkChmodDashr String
"chmod -R 0755 dir"
prop_checkChmodDashr3 :: Bool
prop_checkChmodDashr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkChmodDashr String
"chmod a-r dir"
checkChmodDashr :: CommandCheck
checkChmodDashr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"chmod") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> m ()
f Token
t = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
check :: Token -> m ()
check Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
flag <- Token -> Maybe String
getLiteralString Token
t
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
flag String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"-r"
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2253 String
"Use -R to recurse, or explicitly a-r to remove read permissions."
prop_checkXargsDashi1 :: Bool
prop_checkXargsDashi1 = CommandCheck -> String -> Bool
verify CommandCheck
checkXargsDashi String
"xargs -i{} echo {}"
prop_checkXargsDashi2 :: Bool
prop_checkXargsDashi2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkXargsDashi String
"xargs -I{} echo {}"
prop_checkXargsDashi3 :: Bool
prop_checkXargsDashi3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkXargsDashi String
"xargs sed -i -e foo"
prop_checkXargsDashi4 :: Bool
prop_checkXargsDashi4 = CommandCheck -> String -> Bool
verify CommandCheck
checkXargsDashi String
"xargs -e sed -i foo"
prop_checkXargsDashi5 :: Bool
prop_checkXargsDashi5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkXargsDashi String
"xargs -x sed -i foo"
checkXargsDashi :: CommandCheck
checkXargsDashi = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"xargs") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
where
f :: Token -> m ()
f Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
[(String, (Token, Token))]
opts <- [Token] -> Maybe [(String, (Token, Token))]
parseOpts ([Token] -> Maybe [(String, (Token, Token))])
-> [Token] -> Maybe [(String, (Token, Token))]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
(Token
option, Token
value) <- String -> [(String, (Token, Token))] -> Maybe (Token, Token)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup String
"i" [(String, (Token, Token))]
opts
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
option) Code
2267 String
"GNU xargs -i is deprecated in favor of -I{}"
parseOpts :: [Token] -> Maybe [(String, (Token, Token))]
parseOpts = String -> [Token] -> Maybe [(String, (Token, Token))]
getBsdOpts String
"0oprtxadR:S:J:L:l:n:P:s:e:E:i:I:"
prop_checkArgComparison1 :: Bool
prop_checkArgComparison1 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkArgComparison String
"declare") String
"declare a = b"
prop_checkArgComparison2 :: Bool
prop_checkArgComparison2 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkArgComparison String
"declare") String
"declare a =b"
prop_checkArgComparison3 :: Bool
prop_checkArgComparison3 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkArgComparison String
"declare") String
"declare a=b"
prop_checkArgComparison4 :: Bool
prop_checkArgComparison4 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkArgComparison String
"export") String
"export a +=b"
prop_checkArgComparison7 :: Bool
prop_checkArgComparison7 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkArgComparison String
"declare") String
"declare -a +i foo"
prop_checkArgComparison8 :: Bool
prop_checkArgComparison8 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkArgComparison String
"let") String
"let x = 0"
checkArgComparison :: String -> CommandCheck
checkArgComparison String
cmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
cmd) Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
wordsWithEqual
where
wordsWithEqual :: Token -> m ()
wordsWithEqual Token
t = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
check :: Token -> m ()
check Token
arg = do
Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
str <- Token -> Maybe String
getLeadingUnquotedString Token
arg
case String
str of
Char
'=':String
_ ->
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
headId Token
arg) Code
2290 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
String
"Remove spaces around = to assign."
Char
'+':Char
'=':String
_ ->
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
headId Token
arg) Code
2290 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
String
"Remove spaces around += to append."
String
_ -> Maybe (m ())
forall a. Maybe a
Nothing
Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
cmd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"let") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
Token
token <- Token -> Maybe Token
getTrailingUnquotedLiteral Token
arg
String
str <- Token -> Maybe String
getLiteralString Token
token
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
"=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
str
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2290 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
String
"Remove spaces around = to assign."
headId :: Token -> Id
headId Token
t =
case Token
t of
T_NormalWord Id
_ (Token
x:[Token]
_) -> Token -> Id
getId Token
x
Token
_ -> Token -> Id
getId Token
t
prop_checkMaskedReturns1 :: Bool
prop_checkMaskedReturns1 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"local") String
"f() { local a=$(false); }"
prop_checkMaskedReturns2 :: Bool
prop_checkMaskedReturns2 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare a=$(false)"
prop_checkMaskedReturns3 :: Bool
prop_checkMaskedReturns3 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare a=\"`false`\""
prop_checkMaskedReturns4 :: Bool
prop_checkMaskedReturns4 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"readonly") String
"readonly a=$(false)"
prop_checkMaskedReturns5 :: Bool
prop_checkMaskedReturns5 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"readonly") String
"readonly a=\"`false`\""
prop_checkMaskedReturns6 :: Bool
prop_checkMaskedReturns6 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare a; a=$(false)"
prop_checkMaskedReturns7 :: Bool
prop_checkMaskedReturns7 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"local") String
"f() { local -r a=$(false); }"
prop_checkMaskedReturns8 :: Bool
prop_checkMaskedReturns8 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"readonly") String
"a=$(false); readonly a"
prop_checkMaskedReturns9 :: Bool
prop_checkMaskedReturns9 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"#!/bin/ksh\n f() { typeset -r x=$(false); }"
prop_checkMaskedReturns10 :: Bool
prop_checkMaskedReturns10 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"#!/bin/ksh\n function f { typeset -r x=$(false); }"
prop_checkMaskedReturns11 :: Bool
prop_checkMaskedReturns11 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"#!/bin/bash\n f() { typeset -r x=$(false); }"
prop_checkMaskedReturns12 :: Bool
prop_checkMaskedReturns12 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"typeset -r x=$(false);"
prop_checkMaskedReturns13 :: Bool
prop_checkMaskedReturns13 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"f() { typeset -g x=$(false); }"
prop_checkMaskedReturns14 :: Bool
prop_checkMaskedReturns14 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare x=${ false; }"
prop_checkMaskedReturns15 :: Bool
prop_checkMaskedReturns15 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"f() { declare x=$(false); }"
checkMaskedReturns :: String -> CommandCheck
checkMaskedReturns String
str = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
str) Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
checkCmd
where
checkCmd :: Token -> m ()
checkCmd Token
t = do
[Token]
path <- Token -> m [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
Shell
shell <- (Parameters -> Shell) -> m Shell
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Shell
shellType
Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
String
name <- Token -> Maybe String
getCommandName Token
t
let flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t)
let hasDashR :: Bool
hasDashR = String
"r" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
let hasDashG :: Bool
hasDashG = String
"g" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
let isInScopedFunction :: Bool
isInScopedFunction = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Shell -> Token -> Bool
isScopedFunction Shell
shell) [Token]
path
let isLocal :: Bool
isLocal = Bool -> Bool
not Bool
hasDashG Bool -> Bool -> Bool
&& String -> Bool
isLocalInFunction String
name Bool -> Bool -> Bool
&& Bool
isInScopedFunction
let isReadOnly :: Bool
isReadOnly = String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"readonly" Bool -> Bool -> Bool
|| Bool
hasDashR
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Bool
isLocal Bool -> Bool -> Bool
&& Bool
isReadOnly
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArgs ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
checkArgs :: Token -> m ()
checkArgs (T_Assignment Id
id AssignmentMode
_ String
_ [Token]
_ Token
word) | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
hasReturn ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word =
Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2155 String
"Declare and assign separately to avoid masking return values."
checkArgs Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
isLocalInFunction :: String -> Bool
isLocalInFunction = (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"local", String
"declare", String
"typeset"])
isScopedFunction :: Shell -> Token -> Bool
isScopedFunction Shell
shell Token
t =
case Token
t of
T_BatsTest {} -> Bool
True
T_Function Id
_ (FunctionKeyword Bool
hasFunction) FunctionParentheses
_ String
_ Token
_ -> Shell
shell Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
/= Shell
Ksh Bool -> Bool -> Bool
|| Bool
hasFunction
Token
_ -> Bool
False
hasReturn :: Token -> Bool
hasReturn Token
t = case Token
t of
T_Backticked {} -> Bool
True
T_DollarExpansion {} -> Bool
True
T_DollarBraceCommandExpansion {} -> Bool
True
Token
_ -> Bool
False
prop_checkUnquotedEchoSpaces1 :: Bool
prop_checkUnquotedEchoSpaces1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnquotedEchoSpaces String
"echo foo bar"
prop_checkUnquotedEchoSpaces2 :: Bool
prop_checkUnquotedEchoSpaces2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo foo"
prop_checkUnquotedEchoSpaces3 :: Bool
prop_checkUnquotedEchoSpaces3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo foo bar"
prop_checkUnquotedEchoSpaces4 :: Bool
prop_checkUnquotedEchoSpaces4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo 'foo bar'"
prop_checkUnquotedEchoSpaces5 :: Bool
prop_checkUnquotedEchoSpaces5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo a > myfile.txt b"
prop_checkUnquotedEchoSpaces6 :: Bool
prop_checkUnquotedEchoSpaces6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
" echo foo\\\n bar"
checkUnquotedEchoSpaces :: CommandCheck
checkUnquotedEchoSpaces = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"echo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
check
where
check :: Token -> m ()
check Token
t = do
let args :: [Token]
args = Token -> [Token]
arguments Token
t
Map Id (Position, Position)
m <- (Parameters -> Map Id (Position, Position))
-> m (Map Id (Position, Position))
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Map Id (Position, Position)
tokenPositions
Maybe Token
redir <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
let positions :: [(Position, Position)]
positions = (Token -> Maybe (Position, Position))
-> [Token] -> [(Position, Position)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Token
c -> Id -> Map Id (Position, Position) -> Maybe (Position, Position)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
c) Map Id (Position, Position)
m) [Token]
args
let pairs :: [((Position, Position), (Position, Position))]
pairs = [(Position, Position)]
-> [(Position, Position)]
-> [((Position, Position), (Position, Position))]
forall a b. [a] -> [b] -> [(a, b)]
zip [(Position, Position)]
positions (Int -> [(Position, Position)] -> [(Position, Position)]
forall a. Int -> [a] -> [a]
drop Int
1 [(Position, Position)]
positions)
(T_Redirecting Id
_ [Token]
redirTokens Token
_) <- Maybe Token
redir
let redirPositions :: [Position]
redirPositions = (Token -> Maybe Position) -> [Token] -> [Position]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Token
c -> (Position, Position) -> Position
forall a b. (a, b) -> a
fst ((Position, Position) -> Position)
-> Maybe (Position, Position) -> Maybe Position
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Id -> Map Id (Position, Position) -> Maybe (Position, Position)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
c) Map Id (Position, Position)
m) [Token]
redirTokens
Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (((Position, Position), (Position, Position)) -> Bool)
-> [((Position, Position), (Position, Position))] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ([Position] -> ((Position, Position), (Position, Position)) -> Bool
forall (t :: * -> *).
Foldable t =>
t Position -> ((Position, Position), (Position, Position)) -> Bool
hasSpacesBetween [Position]
redirPositions) [((Position, Position), (Position, Position))]
pairs
m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2291 String
"Quote repeated spaces to avoid them collapsing into one."
hasSpacesBetween :: t Position -> ((Position, Position), (Position, Position)) -> Bool
hasSpacesBetween t Position
redirs ((Position
a,Position
b), (Position
c,Position
d)) =
Position -> Code
posLine Position
a Code -> Code -> Bool
forall a. Eq a => a -> a -> Bool
== Position -> Code
posLine Position
d
Bool -> Bool -> Bool
&& ((Position -> Code
posColumn Position
c) Code -> Code -> Code
forall a. Num a => a -> a -> a
- (Position -> Code
posColumn Position
b)) Code -> Code -> Bool
forall a. Ord a => a -> a -> Bool
>= Code
4
Bool -> Bool -> Bool
&& Bool -> Bool
not ((Position -> Bool) -> t Position -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Position
x -> Position
b Position -> Position -> Bool
forall a. Ord a => a -> a -> Bool
< Position
x Bool -> Bool -> Bool
&& Position
x Position -> Position -> Bool
forall a. Ord a => a -> a -> Bool
< Position
c) t Position
redirs)
prop_checkEvalArray1 :: Bool
prop_checkEvalArray1 = CommandCheck -> String -> Bool
verify CommandCheck
checkEvalArray String
"eval $@"
prop_checkEvalArray2 :: Bool
prop_checkEvalArray2 = CommandCheck -> String -> Bool
verify CommandCheck
checkEvalArray String
"eval \"${args[@]}\""
prop_checkEvalArray3 :: Bool
prop_checkEvalArray3 = CommandCheck -> String -> Bool
verify CommandCheck
checkEvalArray String
"eval \"${args[@]@Q}\""
prop_checkEvalArray4 :: Bool
prop_checkEvalArray4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkEvalArray String
"eval \"${args[*]@Q}\""
prop_checkEvalArray5 :: Bool
prop_checkEvalArray5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkEvalArray String
"eval \"$*\""
checkEvalArray :: CommandCheck
checkEvalArray = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"eval") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token -> [Token]) -> [Token] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
getWordParts ([Token] -> [Token]) -> (Token -> [Token]) -> Token -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
where
check :: Token -> f ()
check Token
t =
Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isArrayExpansion Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
if Token -> Bool
isEscaped Token
t
then Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2293 String
"When eval'ing @Q-quoted words, use * rather than @ as the index."
else Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2294 String
"eval negates the benefit of arrays. Drop eval to preserve whitespace/symbols (or eval as string)."
isEscaped :: Token -> Bool
isEscaped Token
q =
case Token
q of
T_DollarBraced Id
_ Bool
_ Token
l -> Char
'Q' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String -> String
getBracedModifier ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l)
Token
_ -> Bool
False
return []
runTests :: IO Bool
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])