{-
    Copyright 2012-2022 Vidar Holen

    This file is part of ShellCheck.
    https://www.shellcheck.net

    ShellCheck is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ShellCheck is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-}

-- This module contains checks that examine specific commands by name.
module ShellCheck.Checks.Commands (checker, optionalChecks, ShellCheck.Checks.Commands.runTests) where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib
import ShellCheck.CFG
import qualified ShellCheck.CFGAnalysis as CF
import ShellCheck.Data
import ShellCheck.Interface
import ShellCheck.Parser
import ShellCheck.Prelude
import ShellCheck.Regex

import Control.Monad
import Control.Monad.RWS
import Data.Char
import Data.Functor.Identity
import qualified Data.Graph.Inductive.Graph as G
import Data.List
import Data.Maybe
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)

import Debug.Trace -- STRIP

data CommandName = Exactly String | Basename String
    deriving (CommandName -> CommandName -> Bool
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
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
Ord)

data CommandCheck =
    CommandCheck CommandName (Token -> Analysis)


verify :: CommandCheck -> String -> Bool
verify :: CommandCheck -> [Char] -> Bool
verify CommandCheck
f [Char]
s = Checker -> [Char] -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) [Char]
s forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just Bool
True
verifyNot :: CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
f [Char]
s = Checker -> [Char] -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) [Char]
s forall a. Eq a => a -> a -> 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
    ]
    forall a. [a] -> [a] -> [a]
++ forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkArgComparison ([Char]
"alias" forall a. a -> [a] -> [a]
: [[Char]]
declaringCommands)
    forall a. [a] -> [a] -> [a]
++ forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkMaskedReturns [[Char]]
declaringCommands
    forall a. [a] -> [a] -> [a]
++ forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkMultipleDeclaring [[Char]]
declaringCommands
    forall a. [a] -> [a] -> [a]
++ forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkBackreferencingDeclaration [[Char]]
declaringCommands


optionalChecks :: [CheckDescription]
optionalChecks = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst [(CheckDescription, CommandCheck)]
optionalCommandChecks
optionalCommandChecks :: [(CheckDescription, CommandCheck)]
optionalCommandChecks :: [(CheckDescription, CommandCheck)]
optionalCommandChecks = [
    (CheckDescription
newCheckDescription {
        cdName :: [Char]
cdName = [Char]
"deprecate-which",
        cdDescription :: [Char]
cdDescription = [Char]
"Suggest 'command -v' instead of 'which'",
        cdPositive :: [Char]
cdPositive = [Char]
"which javac",
        cdNegative :: [Char]
cdNegative = [Char]
"command -v javac"
    }, CommandCheck
checkWhich)
    ]
optionalCheckMap :: Map [Char] CommandCheck
optionalCheckMap = forall k a. Ord k => [(k, a)] -> Map k a
M.fromList forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (\(CheckDescription
desc, CommandCheck
check) -> (CheckDescription -> [Char]
cdName CheckDescription
desc, CommandCheck
check)) [(CheckDescription, CommandCheck)]
optionalCommandChecks

prop_verifyOptionalExamples :: Bool
prop_verifyOptionalExamples = 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 -> [Char] -> Bool
verify CommandCheck
check (CheckDescription -> [Char]
cdPositive CheckDescription
desc)
      Bool -> Bool -> Bool
&& CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
check (CheckDescription -> [Char]
cdNegative CheckDescription
desc)

-- Run a check against the getopt parser. If it fails, the lists are empty.
checkGetOpts :: [Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
str [[Char]]
flags [[Char]]
args [Token] -> Maybe [([Char], (a, Token))]
f =
    [[Char]]
flags forall a. Eq a => a -> a -> Bool
== [[Char]]
actualFlags Bool -> Bool -> Bool
&& [[Char]]
args forall a. Eq a => a -> a -> Bool
== [[Char]]
actualArgs
  where
    toTokens :: [Char] -> [Token]
toTokens = forall a b. (a -> b) -> [a] -> [b]
map (Id -> [Char] -> Token
T_Literal (Int -> Id
Id Int
0)) forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
words
    opts :: [([Char], (a, Token))]
opts = forall a. a -> Maybe a -> a
fromMaybe [] forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe [([Char], (a, Token))]
f ([Char] -> [Token]
toTokens [Char]
str)
    actualFlags :: [[Char]]
actualFlags = forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null) forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst [([Char], (a, Token))]
opts
    actualArgs :: [[Char]]
actualArgs = [Token -> [Char]
onlyLiteralString Token
x | ([Char]
"", (a
_, Token
x)) <- [([Char], (a, Token))]
opts]

-- Short options
prop_checkGetOptsS1 :: Bool
prop_checkGetOptsS1 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-f x" [[Char]
"f"] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"f:" []
prop_checkGetOptsS2 :: Bool
prop_checkGetOptsS2 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-fx" [[Char]
"f"] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"f:" []
prop_checkGetOptsS3 :: Bool
prop_checkGetOptsS3 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-f -x" [[Char]
"f", [Char]
"x"] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"fx" []
prop_checkGetOptsS4 :: Bool
prop_checkGetOptsS4 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-f -x" [[Char]
"f"] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"f:" []
prop_checkGetOptsS5 :: Bool
prop_checkGetOptsS5 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-fx" [] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"fx:" []

prop_checkGenericOptsS1 :: Bool
prop_checkGenericOptsS1 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-f x" [[Char]
"f"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts
prop_checkGenericOptsS2 :: Bool
prop_checkGenericOptsS2 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-abc x" [[Char]
"a", [Char]
"b", [Char]
"c"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts
prop_checkGenericOptsS3 :: Bool
prop_checkGenericOptsS3 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-abc -x" [[Char]
"a", [Char]
"b", [Char]
"c", [Char]
"x"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts
prop_checkGenericOptsS4 :: Bool
prop_checkGenericOptsS4 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-x" [[Char]
"x"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts

-- Long options
prop_checkGetOptsL1 :: Bool
prop_checkGetOptsL1 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"--foo=bar baz" [[Char]
"foo"] [[Char]
"baz"] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
False) [Char]
"" [([Char]
"foo", Bool
True)]
prop_checkGetOptsL2 :: Bool
prop_checkGetOptsL2 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"--foo bar baz" [[Char]
"foo"] [[Char]
"baz"] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
False) [Char]
"" [([Char]
"foo", Bool
True)]
prop_checkGetOptsL3 :: Bool
prop_checkGetOptsL3 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"--foo baz" [[Char]
"foo"] [[Char]
"baz"] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"" []
prop_checkGetOptsL4 :: Bool
prop_checkGetOptsL4 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"--foo baz" [] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
False) [Char]
"" []

prop_checkGenericOptsL1 :: Bool
prop_checkGenericOptsL1 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"--foo=bar" [[Char]
"foo"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts
prop_checkGenericOptsL2 :: Bool
prop_checkGenericOptsL2 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"--foo bar" [[Char]
"foo"] [[Char]
"bar"] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts
prop_checkGenericOptsL3 :: Bool
prop_checkGenericOptsL3 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-x --foo" [[Char]
"x", [Char]
"foo"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts

-- Know when to terminate
prop_checkGetOptsT1 :: Bool
prop_checkGetOptsT1 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-a x -b" [[Char]
"a", [Char]
"b"] [[Char]
"x"] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"ab" []
prop_checkGetOptsT2 :: Bool
prop_checkGetOptsT2 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-a x -b" [[Char]
"a"] [[Char]
"x",[Char]
"-b"] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
False, Bool
True) [Char]
"ab" []
prop_checkGetOptsT3 :: Bool
prop_checkGetOptsT3 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-a -- -b" [[Char]
"a"] [[Char]
"-b"] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"ab" []
prop_checkGetOptsT4 :: Bool
prop_checkGetOptsT4 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-a -- -b" [[Char]
"a", [Char]
"b"] [] forall a b. (a -> b) -> a -> b
$ (Bool, Bool)
-> [Char]
-> [([Char], Bool)]
-> [Token]
-> Maybe [([Char], (Token, Token))]
getOpts (Bool
True, Bool
True) [Char]
"a:b" []

prop_checkGenericOptsT1 :: Bool
prop_checkGenericOptsT1 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-x -- -y" [[Char]
"x"] [[Char]
"-y"] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts
prop_checkGenericOptsT2 :: Bool
prop_checkGenericOptsT2 = forall {a}.
[Char]
-> [[Char]]
-> [[Char]]
-> ([Token] -> Maybe [([Char], (a, Token))])
-> Bool
checkGetOpts [Char]
"-xy --" [[Char]
"x", [Char]
"y"] [] forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [([Char], (Token, Token))]
getGenericOpts


buildCommandMap :: [CommandCheck] -> M.Map CommandName (Token -> Analysis)
buildCommandMap :: [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck forall k a. Map k a
M.empty
  where
    addCheck :: Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck Map CommandName (Token -> Analysis)
map (CommandCheck CommandName
name Token -> Analysis
function) =
        forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith forall a. (a -> Analysis) -> (a -> Analysis) -> a -> Analysis
composeAnalyzers CommandName
name Token -> Analysis
function Map CommandName (Token -> Analysis)
map


checkCommand :: M.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)) = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
    [Char]
name <- Token -> Maybe [Char]
getLiteralString Token
cmd
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
        if Char
'/' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
name
        then
            forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Basename forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
basename [Char]
name) Map CommandName (Token -> Analysis)
map Token
t
        else if [Char]
name forall a. Eq a => a -> a -> Bool
== [Char]
"builtin" Bool -> Bool -> Bool
&& Bool -> Bool
not (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 :: [Char]
selectedBuiltin = forall a. a -> Maybe a -> a
fromMaybe [Char]
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe [Char]
getLiteralString forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> a
head forall a b. (a -> b) -> a -> b
$ [Token]
rest
            in forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Exactly [Char]
selectedBuiltin) Map CommandName (Token -> Analysis)
map Token
t'
        else do
            forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Exactly [Char]
name) Map CommandName (Token -> Analysis)
map Token
t
            forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Basename [Char]
name) Map CommandName (Token -> Analysis)
map Token
t

  where
    basename :: [Char] -> [Char]
basename = forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'/') forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse
checkCommand Map CommandName (Token -> Analysis)
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

getChecker :: [CommandCheck] -> Checker
getChecker :: [CommandCheck] -> Checker
getChecker [CommandCheck]
list = Checker {
    perScript :: Root -> Analysis
perScript = forall a b. a -> b -> a
const forall a b. (a -> b) -> a -> b
$ 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 forall a b. (a -> b) -> a -> b
$ [CommandCheck]
commandChecks forall a. [a] -> [a] -> [a]
++ [CommandCheck]
optionals
  where
    keys :: [[Char]]
keys = AnalysisSpec -> [[Char]]
asOptionalChecks AnalysisSpec
spec
    optionals :: [CommandCheck]
optionals =
        if [Char]
"all" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
keys
        then forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(CheckDescription, CommandCheck)]
optionalCommandChecks
        else forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\[Char]
x -> forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup [Char]
x Map [Char] CommandCheck
optionalCheckMap) [[Char]]
keys

prop_checkTr1 :: Bool
prop_checkTr1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTr [Char]
"tr [a-f] [A-F]"
prop_checkTr2 :: Bool
prop_checkTr2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTr [Char]
"tr 'a-z' 'A-Z'"
prop_checkTr2a :: Bool
prop_checkTr2a = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTr [Char]
"tr '[a-z]' '[A-Z]'"
prop_checkTr3 :: Bool
prop_checkTr3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr -d '[:lower:]'"
prop_checkTr3a :: Bool
prop_checkTr3a = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr -d '[:upper:]'"
prop_checkTr3b :: Bool
prop_checkTr3b = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr -d '|/_[:upper:]'"
prop_checkTr4 :: Bool
prop_checkTr4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"ls [a-z]"
prop_checkTr5 :: Bool
prop_checkTr5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTr [Char]
"tr foo bar"
prop_checkTr6 :: Bool
prop_checkTr6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTr [Char]
"tr 'hello' 'world'"
prop_checkTr8 :: Bool
prop_checkTr8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr aeiou _____"
prop_checkTr9 :: Bool
prop_checkTr9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"a-z n-za-m"
prop_checkTr10 :: Bool
prop_checkTr10 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr --squeeze-repeats rl lr"
prop_checkTr11 :: Bool
prop_checkTr11 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr abc '[d*]'"
prop_checkTr12 :: Bool
prop_checkTr12 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTr [Char]
"tr '[=e=]' 'e'"
checkTr :: CommandCheck
checkTr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"tr") (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f 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 = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme?
        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
w) Code
2060 [Char]
"Quote parameters to tr to prevent glob expansion."
    f Token
word =
      case Token -> Maybe [Char]
getLiteralString Token
word of
        Just [Char]
"a-z" -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
word) Code
2018 [Char]
"Use '[:lower:]' to support accents and foreign alphabets."
        Just [Char]
"A-Z" -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
word) Code
2019 [Char]
"Use '[:upper:]' to support accents and foreign alphabets."
        Just [Char]
s -> do  -- Eliminate false positives by only looking for dupes in SET2?
          forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not ([Char]
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s Bool -> Bool -> Bool
|| [Char]
"[:" forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` [Char]
s) Bool -> Bool -> Bool
&& [Char] -> Bool
duplicated [Char]
s) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
word) Code
2020 [Char]
"tr replaces sets of chars, not words (mentioned due to duplicates)."
          forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([Char]
"[:" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s Bool -> Bool -> Bool
|| [Char]
"[=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s) forall a b. (a -> b) -> a -> b
$
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
"[" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s Bool -> Bool -> Bool
&& [Char]
"]" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` [Char]
s Bool -> Bool -> Bool
&& (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Char]
s forall a. Ord a => a -> a -> Bool
> Int
2) Bool -> Bool -> Bool
&& (Char
'*' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Char]
s)) forall a b. (a -> b) -> a -> b
$
              forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
word) Code
2021 [Char]
"Don't use [] around classes in tr, it replaces literal square brackets."
        Maybe [Char]
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    duplicated :: [Char] -> Bool
duplicated [Char]
s =
        let relevant :: [Char]
relevant = forall a. (a -> Bool) -> [a] -> [a]
filter Char -> Bool
isAlpha [Char]
s
        in [Char]
relevant forall a. Eq a => a -> a -> Bool
/= forall a. Eq a => [a] -> [a]
nub [Char]
relevant

prop_checkFindNameGlob1 :: Bool
prop_checkFindNameGlob1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindNameGlob [Char]
"find / -name *.php"
prop_checkFindNameGlob2 :: Bool
prop_checkFindNameGlob2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindNameGlob [Char]
"find / -type f -ipath *(foo)"
prop_checkFindNameGlob3 :: Bool
prop_checkFindNameGlob3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindNameGlob [Char]
"find * -name '*.php'"
checkFindNameGlob :: CommandCheck
checkFindNameGlob = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)  where
    acceptsGlob :: [Char] -> Bool
acceptsGlob [Char]
s = [Char]
s forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ [Char]
"-ilname", [Char]
"-iname", [Char]
"-ipath", [Char]
"-iregex", [Char]
"-iwholename", [Char]
"-lname", [Char]
"-name", [Char]
"-path", [Char]
"-regex", [Char]
"-wholename" ]
    f :: [Token] -> m ()
f [] = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f (Token
x:[Token]
xs) = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall {m :: * -> *} {b}.
MonadWriter [TokenComment] m =>
Token -> (Token -> m b) -> Token -> m b
g (forall a b. a -> b -> a
const forall a b. (a -> b) -> a -> b
$ 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
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Token -> Maybe [Char]
getLiteralString Token
a) forall a b. (a -> b) -> a -> b
$ \[Char]
s -> forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char] -> Bool
acceptsGlob [Char]
s Bool -> Bool -> Bool
&& Token -> Bool
isGlob Token
b) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
b) Code
2061 forall a b. (a -> b) -> a -> b
$ [Char]
"Quote the parameter to " forall a. [a] -> [a] -> [a]
++ [Char]
s forall a. [a] -> [a] -> [a]
++ [Char]
" so the shell won't interpret it."
        Token -> m b
acc Token
b


prop_checkExpr :: Bool
prop_checkExpr = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"foo=$(expr 3 + 2)"
prop_checkExpr2 :: Bool
prop_checkExpr2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"foo=`echo \\`expr 3 + 2\\``"
prop_checkExpr3 :: Bool
prop_checkExpr3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExpr [Char]
"foo=$(expr foo : regex)"
prop_checkExpr4 :: Bool
prop_checkExpr4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExpr [Char]
"foo=$(expr foo \\< regex)"
prop_checkExpr5 :: Bool
prop_checkExpr5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr match foo bar"
prop_checkExpr6 :: Bool
prop_checkExpr6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr foo : fo*"
prop_checkExpr7 :: Bool
prop_checkExpr7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr 5 -3"
prop_checkExpr8 :: Bool
prop_checkExpr8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr \"$@\""
prop_checkExpr9 :: Bool
prop_checkExpr9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr 5 $rest"
prop_checkExpr10 :: Bool
prop_checkExpr10 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr length \"$var\""
prop_checkExpr11 :: Bool
prop_checkExpr11 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr foo > bar"
prop_checkExpr12 :: Bool
prop_checkExpr12 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr 1 | 2"
prop_checkExpr13 :: Bool
prop_checkExpr13 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr 1 * 2"
prop_checkExpr14 :: Bool
prop_checkExpr14 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExpr [Char]
"# shellcheck disable=SC2003\nexpr \"$x\" >=  \"$y\""

checkExpr :: CommandCheck
checkExpr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"expr") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f where
    f :: Token -> m ()
f Token
t = do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [[Char]]
exceptions) ([Token] -> [[Char]]
words forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t)) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2003
                [Char]
"expr is antiquated. Consider rewriting this using $((..)), ${} or [[ ]]."

        case Token -> [Token]
arguments Token
t of
            [Token
lhs, Token
op, Token
rhs] -> do
                forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkOp Token
lhs
                case Token -> [Token]
getWordParts Token
op of
                    [T_Glob Id
_ [Char]
"*"] ->
                        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
op) Code
2304
                            [Char]
"* must be escaped to multiply: \\*. Modern $((x * y)) avoids this issue."
                    [T_Literal Id
_ [Char]
":"] | Token -> Bool
isGlob Token
rhs ->
                        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
rhs) Code
2305
                            [Char]
"Quote regex argument to expr to avoid it expanding as a glob."
                    [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

            [Token
single] | Bool -> Bool
not (Token -> Bool
willSplit Token
single) ->
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
single) Code
2307
                    [Char]
"'expr' expects 3+ arguments but sees 1. Make sure each operator/operand is a separate argument, and escape <>&|."

            [Token
first, Token
second] |
                (forall a. a -> Maybe a -> a
fromMaybe [Char]
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe [Char]
getLiteralString Token
first) forall a. Eq a => a -> a -> Bool
/= [Char]
"length"
                  Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
willSplit Token
first Bool -> Bool -> Bool
|| Token -> Bool
willSplit Token
second) -> do
                    forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkOp Token
first
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2307
                        [Char]
"'expr' expects 3+ arguments, but sees 2. Make sure each operator/operand is a separate argument, and escape <>&|."

            (Token
first:[Token]
rest) -> do
                forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkOp Token
first
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
rest forall a b. (a -> b) -> a -> b
$ \Token
t ->
                    -- We already find 95%+ of multiplication and regex earlier, so don't bother classifying this further.
                    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
t) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2306 [Char]
"Escape glob characters in arguments to expr to avoid pathname expansion."

            [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    -- These operators are hard to replicate in POSIX
    exceptions :: [[Char]]
exceptions = [ [Char]
":", [Char]
"<", [Char]
">", [Char]
"<=", [Char]
">=",
        -- We can offer better suggestions for these
        [Char]
"match", [Char]
"length", [Char]
"substr", [Char]
"index"]
    words :: [Token] -> [[Char]]
words = forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe [Char]
getLiteralString

    checkOp :: Token -> m ()
checkOp Token
side =
        case Token -> Maybe [Char]
getLiteralString Token
side of
            Just [Char]
"match" -> [Char] -> m ()
msg [Char]
"'expr match' has unspecified results. Prefer 'expr str : regex'."
            Just [Char]
"length" -> [Char] -> m ()
msg [Char]
"'expr length' has unspecified results. Prefer ${#var}."
            Just [Char]
"substr" -> [Char] -> m ()
msg [Char]
"'expr substr' has unspecified results. Prefer 'cut' or ${var#???}."
            Just [Char]
"index" -> [Char] -> m ()
msg [Char]
"'expr index' has unspecified results. Prefer x=${var%%[chars]*}; $((${#x}+1))."
            Maybe [Char]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
      where
        msg :: [Char] -> m ()
msg = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
side) Code
2308


prop_checkGrepRe1 :: Bool
prop_checkGrepRe1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkGrepRe [Char]
"cat foo | grep *.mp3"
prop_checkGrepRe2 :: Bool
prop_checkGrepRe2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkGrepRe [Char]
"grep -Ev cow*test *.mp3"
prop_checkGrepRe3 :: Bool
prop_checkGrepRe3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkGrepRe [Char]
"grep --regex=*.mp3 file"
prop_checkGrepRe4 :: Bool
prop_checkGrepRe4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep foo *.mp3"
prop_checkGrepRe5 :: Bool
prop_checkGrepRe5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep-v  --regex=moo *"
prop_checkGrepRe6 :: Bool
prop_checkGrepRe6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep foo \\*.mp3"
prop_checkGrepRe7 :: Bool
prop_checkGrepRe7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkGrepRe [Char]
"grep *foo* file"
prop_checkGrepRe8 :: Bool
prop_checkGrepRe8 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkGrepRe [Char]
"ls | grep foo*.jpg"
prop_checkGrepRe9 :: Bool
prop_checkGrepRe9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep '[0-9]*' file"
prop_checkGrepRe10 :: Bool
prop_checkGrepRe10 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep '^aa*' file"
prop_checkGrepRe11 :: Bool
prop_checkGrepRe11 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --include=*.png foo"
prop_checkGrepRe12 :: Bool
prop_checkGrepRe12 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep -F 'Foo*' file"
prop_checkGrepRe13 :: Bool
prop_checkGrepRe13 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep -- -foo bar*"
prop_checkGrepRe14 :: Bool
prop_checkGrepRe14 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep -e -foo bar*"
prop_checkGrepRe15 :: Bool
prop_checkGrepRe15 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --regex -foo bar*"
prop_checkGrepRe16 :: Bool
prop_checkGrepRe16 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --include 'Foo*' file"
prop_checkGrepRe17 :: Bool
prop_checkGrepRe17 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --exclude 'Foo*' file"
prop_checkGrepRe18 :: Bool
prop_checkGrepRe18 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --exclude-dir 'Foo*' file"
prop_checkGrepRe19 :: Bool
prop_checkGrepRe19 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkGrepRe [Char]
"grep -- 'Foo*' file"
prop_checkGrepRe20 :: Bool
prop_checkGrepRe20 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --fixed-strings 'Foo*' file"
prop_checkGrepRe21 :: Bool
prop_checkGrepRe21 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep -o 'x*' file"
prop_checkGrepRe22 :: Bool
prop_checkGrepRe22 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep --only-matching 'x*' file"
prop_checkGrepRe23 :: Bool
prop_checkGrepRe23 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkGrepRe [Char]
"grep '.*' file"

checkGrepRe :: CommandCheck
checkGrepRe = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"grep") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check where
    check :: Token -> m ()
check Token
cmd = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
f Token
cmd (Token -> [Token]
arguments Token
cmd)
    -- --regex=*(extglob) doesn't work. Fixme?
    skippable :: [Char] -> Bool
skippable [Char]
s = Bool -> Bool
not ([Char]
"--regex=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s) Bool -> Bool -> Bool
&& [Char]
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s
    f :: Token -> [Token] -> m ()
f Token
_ [] = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f Token
cmd (Token
x:[Token]
r) =
        let str :: [Char]
str = [Char] -> Token -> [Char]
getLiteralStringDef [Char]
"_" Token
x
        in
            if [Char]
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"--", [Char]
"-e", [Char]
"--regex"]
            then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd [Token]
r -- Regex is *after* this
            else
                if [Char] -> Bool
skippable [Char]
str
                then Token -> [Token] -> m ()
f Token
cmd [Token]
r           -- Regex is elsewhere
                else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd (Token
xforall a. a -> [a] -> [a]
:[Token]
r) -- Regex is this

    checkRE :: Token -> [Token] -> m ()
checkRE Token
_ [] = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkRE Token
cmd (Token
re:[Token]
_) = do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
re) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
re) Code
2062 [Char]
"Quote the grep pattern so the shell won't interpret it."

        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
flags) [[Char]]
grepGlobFlags) forall a b. (a -> b) -> a -> b
$ do
            let string :: [Char]
string = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Char]]
oversimplify Token
re
            if [Char] -> Bool
isConfusedGlobRegex [Char]
string then
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
re) Code
2063 [Char]
"Grep uses regex, but this looks like a glob."
              else forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
                Char
char <- [Char] -> Maybe Char
getSuspiciousRegexWildcard [Char]
string
                forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
re) Code
2022 forall a b. (a -> b) -> a -> b
$
                    [Char]
"Note that unlike globs, " forall a. [a] -> [a] -> [a]
++ [Char
char] forall a. [a] -> [a] -> [a]
++ [Char]
"* here matches '" forall a. [a] -> [a] -> [a]
++ [Char
char, Char
char, Char
char] forall a. [a] -> [a] -> [a]
++ [Char]
"' but not '" forall a. [a] -> [a] -> [a]
++ Char -> [Char]
wordStartingWith Char
char forall a. [a] -> [a] -> [a]
++ [Char]
"'."
      where
        flags :: [[Char]]
flags = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall a b. (a -> b) -> a -> b
$ Token -> [(Token, [Char])]
getAllFlags Token
cmd
        grepGlobFlags :: [[Char]]
grepGlobFlags = [[Char]
"fixed-strings", [Char]
"F", [Char]
"include", [Char]
"exclude", [Char]
"exclude-dir", [Char]
"o", [Char]
"only-matching"]

    wordStartingWith :: Char -> [Char]
wordStartingWith Char
c =
        forall {a}. a -> [a] -> a
headOrDefault (Char
cforall a. a -> [a] -> [a]
:[Char]
"test") forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter ([Char
c] forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) forall a b. (a -> b) -> a -> b
$ [[Char]]
candidates
      where
        candidates :: [[Char]]
candidates =
            [[Char]]
sampleWords forall a. [a] -> [a] -> [a]
++ forall a b. (a -> b) -> [a] -> [b]
map (\(Char
x:[Char]
r) -> Char -> Char
toUpper Char
x forall a. a -> [a] -> [a]
: [Char]
r) [[Char]]
sampleWords

    getSuspiciousRegexWildcard :: [Char] -> Maybe Char
getSuspiciousRegexWildcard [Char]
str = case Regex -> [Char] -> Maybe [[Char]]
matchRegex Regex
suspicious [Char]
str of
        Just [[Char
c]] | Bool -> Bool
not ([Char]
str [Char] -> Regex -> Bool
`matches` Regex
contra) -> forall a. a -> Maybe a
Just Char
c
        Maybe [[Char]]
_ -> forall (m :: * -> *) a. MonadFail m => [Char] -> m a
fail [Char]
"looks good"
    suspicious :: Regex
suspicious = [Char] -> Regex
mkRegex [Char]
"([A-Za-z1-9])\\*"
    contra :: Regex
contra = [Char] -> Regex
mkRegex [Char]
"[^a-zA-Z1-9]\\*|[][^$+\\\\]"


prop_checkTrapQuotes1 :: Bool
prop_checkTrapQuotes1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTrapQuotes [Char]
"trap \"echo $num\" INT"
prop_checkTrapQuotes1a :: Bool
prop_checkTrapQuotes1a = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTrapQuotes [Char]
"trap \"echo `ls`\" INT"
prop_checkTrapQuotes2 :: Bool
prop_checkTrapQuotes2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTrapQuotes [Char]
"trap 'echo $num' INT"
prop_checkTrapQuotes3 :: Bool
prop_checkTrapQuotes3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTrapQuotes [Char]
"trap \"echo $((1+num))\" EXIT DEBUG"
checkTrapQuotes :: CommandCheck
checkTrapQuotes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"trap") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
    f :: [Token] -> m ()
f (Token
x:[Token]
_) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkTrap Token
x
    f [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkTrap :: Token -> m ()
checkTrap (T_NormalWord Id
_ [T_DoubleQuoted Id
_ [Token]
rs]) = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkExpansions [Token]
rs
    checkTrap Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    warning :: Id -> m ()
warning Id
id = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2064 [Char]
"Use single quotes, otherwise this expands now rather than when signalled."
    checkExpansions :: Token -> m ()
checkExpansions (T_DollarExpansion Id
id [Token]
_) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions (T_Backticked Id
id [Token]
_) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions (T_DollarBraced Id
id Bool
_ Token
_) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions (T_DollarArithmetic Id
id Token
_) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkReturn1 :: Bool
prop_checkReturn1 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReturn [Char]
"return"
prop_checkReturn2 :: Bool
prop_checkReturn2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReturn [Char]
"return 1"
prop_checkReturn3 :: Bool
prop_checkReturn3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReturn [Char]
"return $var"
prop_checkReturn4 :: Bool
prop_checkReturn4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReturn [Char]
"return $((a|b))"
prop_checkReturn5 :: Bool
prop_checkReturn5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReturn [Char]
"return -1"
prop_checkReturn6 :: Bool
prop_checkReturn6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReturn [Char]
"return 1000"
prop_checkReturn7 :: Bool
prop_checkReturn7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReturn [Char]
"return 'hello world'"
checkReturn :: CommandCheck
checkReturn = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"return") (forall {f :: * -> *}.
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
        (\Id
c -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err Id
c Code
2151 [Char]
"Only one integer 0-255 can be returned. Use stdout for other data.")
        (\Id
c -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err Id
c Code
2152 [Char]
"Can only return 0-255. Other data should be written to stdout."))

prop_checkExit1 :: Bool
prop_checkExit1 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExit [Char]
"exit"
prop_checkExit2 :: Bool
prop_checkExit2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExit [Char]
"exit 1"
prop_checkExit3 :: Bool
prop_checkExit3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExit [Char]
"exit $var"
prop_checkExit4 :: Bool
prop_checkExit4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExit [Char]
"exit $((a|b))"
prop_checkExit5 :: Bool
prop_checkExit5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExit [Char]
"exit -1"
prop_checkExit6 :: Bool
prop_checkExit6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExit [Char]
"exit 1000"
prop_checkExit7 :: Bool
prop_checkExit7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExit [Char]
"exit 'hello world'"
checkExit :: CommandCheck
checkExit = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"exit") (forall {f :: * -> *}.
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
        (\Id
c -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err Id
c Code
2241 [Char]
"The exit status can only be one integer 0-255. Use stdout for other data.")
        (\Id
c -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err Id
c Code
2242 [Char]
"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 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] =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char] -> Bool
isInvalid forall a b. (a -> b) -> a -> b
$ Token -> [Char]
literal Token
value) forall a b. (a -> b) -> a -> b
$
            Id -> f ()
invalid (Token -> Id
getId Token
value)
    f [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isInvalid :: [Char] -> Bool
isInvalid [Char]
s = forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
s Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isDigit) [Char]
s Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => t a -> Int
length [Char]
s forall a. Ord a => a -> a -> Bool
> Int
5
        Bool -> Bool -> Bool
|| let value :: Code
value = (forall a. Read a => [Char] -> a
read [Char]
s :: Integer) in Code
value forall a. Ord a => a -> a -> Bool
> Code
255

    literal :: Token -> [Char]
literal Token
token = forall a. Identity a -> a
runIdentity forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
(Token -> m [Char]) -> Token -> m [Char]
getLiteralStringExt forall {m :: * -> *}. Monad m => Token -> m [Char]
lit Token
token
    lit :: Token -> m [Char]
lit (T_DollarBraced {}) = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"0"
    lit (T_DollarArithmetic {}) = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"0"
    lit (T_DollarExpansion {}) = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"0"
    lit (T_Backticked {}) = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"0"
    lit Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"WTF"


prop_checkFindExecWithSingleArgument1 :: Bool
prop_checkFindExecWithSingleArgument1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindExecWithSingleArgument [Char]
"find . -exec 'cat {} | wc -l' \\;"
prop_checkFindExecWithSingleArgument2 :: Bool
prop_checkFindExecWithSingleArgument2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindExecWithSingleArgument [Char]
"find . -execdir 'cat {} | wc -l' +"
prop_checkFindExecWithSingleArgument3 :: Bool
prop_checkFindExecWithSingleArgument3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindExecWithSingleArgument [Char]
"find . -exec wc -l {} \\;"
checkFindExecWithSingleArgument :: CommandCheck
checkFindExecWithSingleArgument = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") ([Token] -> Analysis
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> Analysis
f = forall (f :: * -> *) a. Functor f => f a -> f ()
void forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> Maybe (m ())
check forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [[a]]
tails
    check :: [Token] -> Maybe (m ())
check (Token
exec:Token
arg:Token
term:[Token]
_) = do
        [Char]
execS <- Token -> Maybe [Char]
getLiteralString Token
exec
        [Char]
termS <- Token -> Maybe [Char]
getLiteralString Token
term
        let cmdS :: [Char]
cmdS = [Char] -> Token -> [Char]
getLiteralStringDef [Char]
" " Token
arg

        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
execS forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"-exec", [Char]
"-execdir"] Bool -> Bool -> Bool
&& [Char]
termS forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
";", [Char]
"+"]
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
cmdS [Char] -> Regex -> Bool
`matches` Regex
commandRegex
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
exec) Code
2150 [Char]
"-exec does not invoke a shell. Rewrite or use -exec sh -c .. ."
    check [Token]
_ = forall a. Maybe a
Nothing
    commandRegex :: Regex
commandRegex = [Char] -> Regex
mkRegex [Char]
"[ |;]"


prop_checkUnusedEchoEscapes1 :: Bool
prop_checkUnusedEchoEscapes1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo 'foo\\nbar\\n'"
prop_checkUnusedEchoEscapes2 :: Bool
prop_checkUnusedEchoEscapes2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo -e 'foi\\nbar'"
prop_checkUnusedEchoEscapes3 :: Bool
prop_checkUnusedEchoEscapes3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo \"n:\\t42\""
prop_checkUnusedEchoEscapes4 :: Bool
prop_checkUnusedEchoEscapes4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo lol"
prop_checkUnusedEchoEscapes5 :: Bool
prop_checkUnusedEchoEscapes5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo -n -e '\n'"
prop_checkUnusedEchoEscapes6 :: Bool
prop_checkUnusedEchoEscapes6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\506'"
prop_checkUnusedEchoEscapes7 :: Bool
prop_checkUnusedEchoEscapes7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\5a'"
prop_checkUnusedEchoEscapes8 :: Bool
prop_checkUnusedEchoEscapes8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\8a'"
prop_checkUnusedEchoEscapes9 :: Bool
prop_checkUnusedEchoEscapes9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\d5a'"
prop_checkUnusedEchoEscapes10 :: Bool
prop_checkUnusedEchoEscapes10 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\x4a'"
prop_checkUnusedEchoEscapes11 :: Bool
prop_checkUnusedEchoEscapes11 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\xat'"
prop_checkUnusedEchoEscapes12 :: Bool
prop_checkUnusedEchoEscapes12 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\xth'"
checkUnusedEchoEscapes :: CommandCheck
checkUnusedEchoEscapes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"echo") forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    hasEscapes :: Regex
hasEscapes = [Char] -> Regex
mkRegex [Char]
"\\\\([rntabefv\\']|[0-7]{1,3}|x([0-9]|[A-F]|[a-f]){1,2})"
    f :: Token -> m ()
f Token
cmd =
        forall {m :: * -> *} {t :: * -> *}.
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Bash, Shell
Ksh] forall a b. (a -> b) -> a -> b
$
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
cmd Token -> [Char] -> Bool
`hasFlag` [Char]
"e") forall a b. (a -> b) -> a -> b
$
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
examine forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd

    examine :: Token -> f ()
examine Token
token = do
        let str :: [Char]
str = Token -> [Char]
onlyLiteralString Token
token
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
str [Char] -> Regex -> Bool
`matches` Regex
hasEscapes) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
token) Code
2028 [Char]
"echo may not expand escape sequences. Use printf."


prop_checkInjectableFindSh1 :: Bool
prop_checkInjectableFindSh1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkInjectableFindSh [Char]
"find . -exec sh -c 'echo {}' \\;"
prop_checkInjectableFindSh2 :: Bool
prop_checkInjectableFindSh2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkInjectableFindSh [Char]
"find . -execdir bash -c 'rm \"{}\"' ';'"
prop_checkInjectableFindSh3 :: Bool
prop_checkInjectableFindSh3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkInjectableFindSh [Char]
"find . -exec sh -c 'rm \"$@\"' _ {} \\;"
checkInjectableFindSh :: CommandCheck
checkInjectableFindSh = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
check forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: [Token] -> m ()
check [Token]
args = do
        let idStrings :: [(Id, [Char])]
idStrings = forall a b. (a -> b) -> [a] -> [b]
map (\Token
x -> (Token -> Id
getId Token
x, Token -> [Char]
onlyLiteralString Token
x)) [Token]
args
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[[Char] -> Bool] -> [(Id, [Char])] -> m ()
match [[Char] -> Bool]
pattern [(Id, [Char])]
idStrings

    match :: [[Char] -> Bool] -> [(Id, [Char])] -> m ()
match [[Char] -> Bool]
_ [] = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    match [] ((Id, [Char])
next:[(Id, [Char])]
_) = forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
(Id, [Char]) -> f ()
action (Id, [Char])
next
    match ([Char] -> Bool
p:[[Char] -> Bool]
tests) ((Id
id, [Char]
arg):[(Id, [Char])]
args) = do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char] -> Bool
p [Char]
arg) forall a b. (a -> b) -> a -> b
$ [[Char] -> Bool] -> [(Id, [Char])] -> m ()
match [[Char] -> Bool]
tests [(Id, [Char])]
args
        [[Char] -> Bool] -> [(Id, [Char])] -> m ()
match ([Char] -> Bool
pforall a. a -> [a] -> [a]
:[[Char] -> Bool]
tests) [(Id, [Char])]
args

    pattern :: [[Char] -> Bool]
pattern = [
        (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"-exec", [Char]
"-execdir"]),
        (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"sh", [Char]
"bash", [Char]
"dash", [Char]
"ksh"]),
        (forall a. Eq a => a -> a -> Bool
== [Char]
"-c")
        ]
    action :: (Id, [Char]) -> f ()
action (Id
id, [Char]
arg) =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
"{}" forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` [Char]
arg) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2156 [Char]
"Injecting filenames is fragile and insecure. Use parameters."


prop_checkFindActionPrecedence1 :: Bool
prop_checkFindActionPrecedence1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindActionPrecedence [Char]
"find . -name '*.wav' -o -name '*.au' -exec rm {} +"
prop_checkFindActionPrecedence2 :: Bool
prop_checkFindActionPrecedence2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindActionPrecedence [Char]
"find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)"
prop_checkFindActionPrecedence3 :: Bool
prop_checkFindActionPrecedence3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindActionPrecedence [Char]
"find . -name '*.wav' -o -name '*.au'"
checkFindActionPrecedence :: CommandCheck
checkFindActionPrecedence = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    pattern :: [Token -> Bool]
pattern = [Token -> Bool
isMatch, forall a b. a -> b -> a
const Bool
True, forall {t :: * -> *}. Foldable t => t [Char] -> Token -> Bool
isParam [[Char]
"-o", [Char]
"-or"], Token -> Bool
isMatch, forall a b. a -> b -> a
const Bool
True, Token -> Bool
isAction]
    f :: [Token] -> m ()
f [Token]
list | forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list forall a. Ord a => a -> a -> Bool
< forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f list :: [Token]
list@(Token
_:[Token]
rest) =
        if forall (t :: * -> *). Foldable t => t Bool -> Bool
and (forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith forall a b. (a -> b) -> a -> b
($) [Token -> Bool]
pattern [Token]
list)
        then forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
warnFor ([Token]
list forall a. [a] -> Int -> a
!! (forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern forall a. Num a => a -> a -> a
- Int
1))
        else [Token] -> m ()
f [Token]
rest
    isMatch :: Token -> Bool
isMatch = forall {t :: * -> *}. Foldable t => t [Char] -> Token -> Bool
isParam [ [Char]
"-name", [Char]
"-regex", [Char]
"-iname", [Char]
"-iregex", [Char]
"-wholename", [Char]
"-iwholename" ]
    isAction :: Token -> Bool
isAction = forall {t :: * -> *}. Foldable t => t [Char] -> Token -> Bool
isParam [ [Char]
"-exec", [Char]
"-execdir", [Char]
"-delete", [Char]
"-print", [Char]
"-print0", [Char]
"-fls", [Char]
"-fprint", [Char]
"-fprint0", [Char]
"-fprintf", [Char]
"-ls", [Char]
"-ok", [Char]
"-okdir", [Char]
"-printf" ]
    isParam :: t [Char] -> Token -> Bool
isParam t [Char]
strs Token
t = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        [Char]
param <- Token -> Maybe [Char]
getLiteralString Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [Char]
param forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t [Char]
strs
    warnFor :: Token -> m ()
warnFor Token
t = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2146 [Char]
"This action ignores everything before the -o. Use \\( \\) to group."


prop_checkMkdirDashPM0 :: Bool
prop_checkMkdirDashPM0 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 a/b"
prop_checkMkdirDashPM1 :: Bool
prop_checkMkdirDashPM1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -pm 0755 $dir"
prop_checkMkdirDashPM2 :: Bool
prop_checkMkdirDashPM2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -vpm 0755 a/b"
prop_checkMkdirDashPM3 :: Bool
prop_checkMkdirDashPM3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -pm 0755 -v a/b"
prop_checkMkdirDashPM4 :: Bool
prop_checkMkdirDashPM4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir --parents --mode=0755 a/b"
prop_checkMkdirDashPM5 :: Bool
prop_checkMkdirDashPM5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir --parents --mode 0755 a/b"
prop_checkMkdirDashPM6 :: Bool
prop_checkMkdirDashPM6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -p --mode=0755 a/b"
prop_checkMkdirDashPM7 :: Bool
prop_checkMkdirDashPM7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir --parents -m 0755 a/b"
prop_checkMkdirDashPM8 :: Bool
prop_checkMkdirDashPM8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p a/b"
prop_checkMkdirDashPM9 :: Bool
prop_checkMkdirDashPM9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -m 0755 a/b"
prop_checkMkdirDashPM10 :: Bool
prop_checkMkdirDashPM10 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir a/b"
prop_checkMkdirDashPM11 :: Bool
prop_checkMkdirDashPM11 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir --parents a/b"
prop_checkMkdirDashPM12 :: Bool
prop_checkMkdirDashPM12 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir --mode=0755 a/b"
prop_checkMkdirDashPM13 :: Bool
prop_checkMkdirDashPM13 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir_func -pm 0755 a/b"
prop_checkMkdirDashPM14 :: Bool
prop_checkMkdirDashPM14 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 singlelevel"
prop_checkMkdirDashPM15 :: Bool
prop_checkMkdirDashPM15 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 ../bin"
prop_checkMkdirDashPM16 :: Bool
prop_checkMkdirDashPM16 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 ../bin/laden"
prop_checkMkdirDashPM17 :: Bool
prop_checkMkdirDashPM17 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 ./bin"
prop_checkMkdirDashPM18 :: Bool
prop_checkMkdirDashPM18 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 ./bin/laden"
prop_checkMkdirDashPM19 :: Bool
prop_checkMkdirDashPM19 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 ./../bin"
prop_checkMkdirDashPM20 :: Bool
prop_checkMkdirDashPM20 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 .././bin"
prop_checkMkdirDashPM21 :: Bool
prop_checkMkdirDashPM21 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMkdirDashPM [Char]
"mkdir -p -m 0755 ../../bin"
checkMkdirDashPM :: CommandCheck
checkMkdirDashPM = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"mkdir") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check
  where
    check :: Token -> m ()
check Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        let flags :: [(Token, [Char])]
flags = Token -> [(Token, [Char])]
getAllFlags Token
t
        (Token, [Char])
dashP <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(Token
_,[Char]
f) -> [Char]
f forall a. Eq a => a -> a -> Bool
== [Char]
"p" Bool -> Bool -> Bool
|| [Char]
f forall a. Eq a => a -> a -> Bool
== [Char]
"parents") [(Token, [Char])]
flags
        (Token, [Char])
dashM <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(Token
_,[Char]
f) -> [Char]
f forall a. Eq a => a -> a -> Bool
== [Char]
"m" Bool -> Bool -> Bool
|| [Char]
f forall a. Eq a => a -> a -> Bool
== [Char]
"mode") [(Token, [Char])]
flags
        -- mkdir -pm 0700 dir  is fine, so is ../dir, but dir/subdir is not.
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
couldHaveSubdirs (forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t)
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ forall a b. (a, b) -> a
fst (Token, [Char])
dashM) Code
2174 [Char]
"When used with -p, -m only applies to the deepest directory."
    couldHaveSubdirs :: Token -> Bool
couldHaveSubdirs Token
t = forall a. a -> Maybe a -> a
fromMaybe Bool
True forall a b. (a -> b) -> a -> b
$ do
        [Char]
name <- Token -> Maybe [Char]
getLiteralString Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Char
'/' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
name Bool -> Bool -> Bool
&& Bool -> Bool
not ([Char]
name [Char] -> Regex -> Bool
`matches` Regex
re)
    re :: Regex
re = [Char] -> Regex
mkRegex [Char]
"^(\\.\\.?\\/)+[^/]+$"


prop_checkNonportableSignals1 :: Bool
prop_checkNonportableSignals1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f 8"
prop_checkNonportableSignals2 :: Bool
prop_checkNonportableSignals2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkNonportableSignals [Char]
"trap f 0"
prop_checkNonportableSignals3 :: Bool
prop_checkNonportableSignals3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkNonportableSignals [Char]
"trap f 14"
prop_checkNonportableSignals4 :: Bool
prop_checkNonportableSignals4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f SIGKILL"
prop_checkNonportableSignals5 :: Bool
prop_checkNonportableSignals5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f 9"
prop_checkNonportableSignals6 :: Bool
prop_checkNonportableSignals6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f stop"
prop_checkNonportableSignals7 :: Bool
prop_checkNonportableSignals7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkNonportableSignals [Char]
"trap 'stop' int"
checkNonportableSignals :: CommandCheck
checkNonportableSignals = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"trap") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f 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 forall a b. (a -> b) -> a -> b
$ Token -> Bool
isFlag Token
first -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
rest
        [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check :: Token -> m ()
check Token
param = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
str <- Token -> Maybe [Char]
getLiteralString Token
param
        let id :: Id
id = Token -> Id
getId Token
param
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Id -> [Char] -> Maybe (m ())
f -> Id -> [Char] -> Maybe (m ())
f Id
id [Char]
str) [
            forall {m :: * -> *} {m :: * -> *}.
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> [Char] -> m (m ())
checkNumeric,
            forall {m :: * -> *} {m :: * -> *}.
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> [Char] -> m (m ())
checkUntrappable
            ]

    checkNumeric :: Id -> [Char] -> m (m ())
checkNumeric Id
id [Char]
str = do
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
str)
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit [Char]
str
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
str forall a. Eq a => a -> a -> Bool
/= [Char]
"0" -- POSIX exit trap
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [[Char]
"1", [Char]
"2", [Char]
"3", [Char]
"6", [Char]
"9", [Char]
"14", [Char]
"15" ] -- XSI
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2172
            [Char]
"Trapping signals by number is not well defined. Prefer signal names."

    checkUntrappable :: Id -> [Char] -> m (m ())
checkUntrappable Id
id [Char]
str = do
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower [Char]
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"kill", [Char]
"9", [Char]
"sigkill", [Char]
"stop", [Char]
"sigstop"]
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err Id
id Code
2173
            [Char]
"SIGKILL/SIGSTOP can not be trapped."


prop_checkInteractiveSu1 :: Bool
prop_checkInteractiveSu1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkInteractiveSu [Char]
"su; rm file; su $USER"
prop_checkInteractiveSu2 :: Bool
prop_checkInteractiveSu2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkInteractiveSu [Char]
"su foo; something; exit"
prop_checkInteractiveSu3 :: Bool
prop_checkInteractiveSu3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkInteractiveSu [Char]
"echo rm | su foo"
prop_checkInteractiveSu4 :: Bool
prop_checkInteractiveSu4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkInteractiveSu [Char]
"su root < script"
checkInteractiveSu :: CommandCheck
checkInteractiveSu = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"su") forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> f ()
f Token
cmd = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => t a -> Int
length (Token -> [Token]
arguments Token
cmd) forall a. Ord a => a -> a -> Bool
<= Int
1) forall a b. (a -> b) -> a -> b
$ do
        [Token]
path <- forall {m :: * -> *}.
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
cmd
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
undirected [Token]
path) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
cmd) Code
2117
                [Char]
"To run commands as another user, use su -c or sudo."

    undirected :: Token -> Bool
undirected (T_Pipeline Id
_ [Token]
_ (Token
_:Token
_:[Token]
_)) = Bool
False
    -- This should really just be modifications to stdin, but meh
    undirected (T_Redirecting Id
_ (Token
_:[Token]
_) Token
_) = Bool
False
    undirected Token
_ = Bool
True


-- This is hard to get right without properly parsing ssh args
prop_checkSshCmdStr1 :: Bool
prop_checkSshCmdStr1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSshCommandString [Char]
"ssh host \"echo $PS1\""
prop_checkSshCmdStr2 :: Bool
prop_checkSshCmdStr2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSshCommandString [Char]
"ssh host \"ls foo\""
prop_checkSshCmdStr3 :: Bool
prop_checkSshCmdStr3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSshCommandString [Char]
"ssh \"$host\""
prop_checkSshCmdStr4 :: Bool
prop_checkSshCmdStr4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSshCommandString [Char]
"ssh -i key \"$host\""
checkSshCommandString :: CommandCheck
checkSshCommandString = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"ssh") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    isOption :: Token -> Bool
isOption Token
x = [Char]
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Char]]
oversimplify Token
x)
    f :: [Token] -> m ()
f [Token]
args =
        case forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Token -> Bool
isOption [Token]
args of
            ([], Token
hostport:r :: [Token]
r@(Token
_:[Token]
_)) -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArg forall a b. (a -> b) -> a -> b
$ forall a. [a] -> a
last [Token]
r
            ([Token], [Token])
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkArg :: Token -> m ()
checkArg (T_NormalWord Id
_ [T_DoubleQuoted Id
id [Token]
parts]) =
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isConstant) [Token]
parts) forall a b. (a -> b) -> a -> b
$
            \Token
x -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
x) Code
2029
                [Char]
"Note that, unescaped, this expands on the client side."
    checkArg Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkPrintfVar1 :: Bool
prop_checkPrintfVar1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf \"Lol: $s\""
prop_checkPrintfVar2 :: Bool
prop_checkPrintfVar2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf 'Lol: $s'"
prop_checkPrintfVar3 :: Bool
prop_checkPrintfVar3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf -v cow $(cmd)"
prop_checkPrintfVar4 :: Bool
prop_checkPrintfVar4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf \"%${count}s\" var"
prop_checkPrintfVar5 :: Bool
prop_checkPrintfVar5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf '%s %s %s' foo bar"
prop_checkPrintfVar6 :: Bool
prop_checkPrintfVar6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf foo bar baz"
prop_checkPrintfVar7 :: Bool
prop_checkPrintfVar7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf -- foo bar baz"
prop_checkPrintfVar8 :: Bool
prop_checkPrintfVar8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%s %s %s' \"${var[@]}\""
prop_checkPrintfVar9 :: Bool
prop_checkPrintfVar9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%s %s %s\\n' *.png"
prop_checkPrintfVar10 :: Bool
prop_checkPrintfVar10 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%s %s %s' foo bar baz"
prop_checkPrintfVar11 :: Bool
prop_checkPrintfVar11 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%(%s%s)T' -1"
prop_checkPrintfVar12 :: Bool
prop_checkPrintfVar12 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf '%s %s\\n' 1 2 3"
prop_checkPrintfVar13 :: Bool
prop_checkPrintfVar13 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%s %s\\n' 1 2 3 4"
prop_checkPrintfVar14 :: Bool
prop_checkPrintfVar14 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf '%*s\\n' 1"
prop_checkPrintfVar15 :: Bool
prop_checkPrintfVar15 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%*s\\n' 1 2"
prop_checkPrintfVar16 :: Bool
prop_checkPrintfVar16 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf $'string'"
prop_checkPrintfVar17 :: Bool
prop_checkPrintfVar17 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf '%-*s\\n' 1"
prop_checkPrintfVar18 :: Bool
prop_checkPrintfVar18 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%-*s\\n' 1 2"
prop_checkPrintfVar19 :: Bool
prop_checkPrintfVar19 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%(%s)T'"
prop_checkPrintfVar20 :: Bool
prop_checkPrintfVar20 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkPrintfVar [Char]
"printf '%d %(%s)T' 42"
prop_checkPrintfVar21 :: Bool
prop_checkPrintfVar21 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf '%d %(%s)T'"
prop_checkPrintfVar22 :: Bool
prop_checkPrintfVar22 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkPrintfVar [Char]
"printf '%s\n%s' foo"

checkPrintfVar :: CommandCheck
checkPrintfVar = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"printf") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
    f :: [Token] -> m ()
f (Token
doubledash:[Token]
rest) | Token -> Maybe [Char]
getLiteralString Token
doubledash forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just [Char]
"--" = [Token] -> m ()
f [Token]
rest
    f (Token
dashv:Token
var:[Token]
rest) | Token -> Maybe [Char]
getLiteralString Token
dashv forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just [Char]
"-v" = [Token] -> m ()
f [Token]
rest
    f (Token
format:[Token]
params) = forall {m :: * -> *} {t :: * -> *}.
(MonadWriter [TokenComment] m, Foldable t) =>
Token -> t Token -> m ()
check Token
format [Token]
params
    f [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check :: Token -> t Token -> m ()
check Token
format t Token
more = do
        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            [Char]
string <- Token -> Maybe [Char]
getLiteralString Token
format
            let formats :: [Char]
formats = [Char] -> [Char]
getPrintfFormats [Char]
string
            let formatCount :: Int
formatCount = forall (t :: * -> *) a. Foldable t => t a -> Int
length [Char]
formats
            let argCount :: Int
argCount = forall (t :: * -> *) a. Foldable t => t a -> Int
length t Token
more
            let pluraliseIfMany :: [Char] -> a -> [Char]
pluraliseIfMany [Char]
word a
n = if a
n forall a. Ord a => a -> a -> Bool
> a
1 then [Char]
word forall a. [a] -> [a] -> [a]
++ [Char]
"s" else [Char]
word

            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ if
                | Int
argCount forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
&& Int
formatCount forall a. Eq a => a -> a -> Bool
== Int
0 ->
                    forall (m :: * -> *) a. Monad m => a -> m a
return () -- This is fine
                | Int
formatCount forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
&& Int
argCount forall a. Ord a => a -> a -> Bool
> Int
0 ->
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
format) Code
2182
                        [Char]
"This printf format string has no variables. Other arguments are ignored."
                | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
mayBecomeMultipleArgs t Token
more ->
                    forall (m :: * -> *) a. Monad m => a -> m a
return () -- We don't know so trust the user
                | Int
argCount forall a. Ord a => a -> a -> Bool
< Int
formatCount Bool -> Bool -> Bool
&& [Char] -> Int -> Bool
onlyTrailingTs [Char]
formats Int
argCount ->
                    forall (m :: * -> *) a. Monad m => a -> m a
return () -- Allow trailing %()Ts since they use the current time
                | Int
argCount forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Int
argCount forall a. Integral a => a -> a -> a
`mod` Int
formatCount forall a. Eq a => a -> a -> Bool
== Int
0 ->
                    forall (m :: * -> *) a. Monad m => a -> m a
return () -- Great: a suitable number of arguments
                | Bool
otherwise ->
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
format) Code
2183 forall a b. (a -> b) -> a -> b
$
                        [Char]
"This format string has " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
formatCount forall a. [a] -> [a] -> [a]
++ [Char]
" " forall a. [a] -> [a] -> [a]
++ forall {a}. (Ord a, Num a) => [Char] -> a -> [Char]
pluraliseIfMany [Char]
"variable" Int
formatCount forall a. [a] -> [a] -> [a]
++
                        [Char]
", but is passed " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show Int
argCount forall a. [a] -> [a] -> [a]
++ forall {a}. (Ord a, Num a) => [Char] -> a -> [Char]
pluraliseIfMany [Char]
" argument" Int
argCount forall a. [a] -> [a] -> [a]
++ [Char]
"."

        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Char
'%' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [[Char]]
oversimplify Token
format) Bool -> Bool -> Bool
|| Token -> Bool
isLiteral Token
format) forall a b. (a -> b) -> a -> b
$
          forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
format) Code
2059
              [Char]
"Don't use variables in the printf format string. Use printf '..%s..' \"$foo\"."
      where
        onlyTrailingTs :: [Char] -> Int -> Bool
onlyTrailingTs [Char]
format Int
argCount =
            forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (forall a. Eq a => a -> a -> Bool
== Char
'T') forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop Int
argCount [Char]
format


prop_checkGetPrintfFormats1 :: Bool
prop_checkGetPrintfFormats1 = [Char] -> [Char]
getPrintfFormats [Char]
"%s" forall a. Eq a => a -> a -> Bool
== [Char]
"s"
prop_checkGetPrintfFormats2 :: Bool
prop_checkGetPrintfFormats2 = [Char] -> [Char]
getPrintfFormats [Char]
"%0*s" forall a. Eq a => a -> a -> Bool
== [Char]
"*s"
prop_checkGetPrintfFormats3 :: Bool
prop_checkGetPrintfFormats3 = [Char] -> [Char]
getPrintfFormats [Char]
"%(%s)T" forall a. Eq a => a -> a -> Bool
== [Char]
"T"
prop_checkGetPrintfFormats4 :: Bool
prop_checkGetPrintfFormats4 = [Char] -> [Char]
getPrintfFormats [Char]
"%d%%%(%s)T" forall a. Eq a => a -> a -> Bool
== [Char]
"dT"
prop_checkGetPrintfFormats5 :: Bool
prop_checkGetPrintfFormats5 = [Char] -> [Char]
getPrintfFormats [Char]
"%bPassed: %d, %bFailed: %d%b, Skipped: %d, %bErrored: %d%b\\n" forall a. Eq a => a -> a -> Bool
== [Char]
"bdbdbdbdb"
prop_checkGetPrintfFormats6 :: Bool
prop_checkGetPrintfFormats6 = [Char] -> [Char]
getPrintfFormats [Char]
"%s%s" forall a. Eq a => a -> a -> Bool
== [Char]
"ss"
prop_checkGetPrintfFormats7 :: Bool
prop_checkGetPrintfFormats7 = [Char] -> [Char]
getPrintfFormats [Char]
"%s\n%s" forall a. Eq a => a -> a -> Bool
== [Char]
"ss"
getPrintfFormats :: [Char] -> [Char]
getPrintfFormats = [Char] -> [Char]
getFormats
  where
    -- Get the arguments in the string as a string of type characters,
    -- e.g. "Hello %s" -> "s" and "%(%s)T %0*d\n" -> "T*d"
    getFormats :: String -> String
    getFormats :: [Char] -> [Char]
getFormats [Char]
string =
        case [Char]
string of
            Char
'%':Char
'%':[Char]
rest -> [Char] -> [Char]
getFormats [Char]
rest
            Char
'%':Char
'(':[Char]
rest ->
                case forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall a. Eq a => a -> a -> Bool
/= Char
')') [Char]
rest of
                    Char
')':Char
c:[Char]
trailing -> Char
c forall a. a -> [a] -> [a]
: [Char] -> [Char]
getFormats [Char]
trailing
                    [Char]
_ -> [Char]
""
            Char
'%':[Char]
rest -> [Char] -> [Char]
regexBasedGetFormats [Char]
rest
            Char
_:[Char]
rest -> [Char] -> [Char]
getFormats [Char]
rest
            [] -> [Char]
""

    regexBasedGetFormats :: [Char] -> [Char]
regexBasedGetFormats [Char]
rest =
        case Regex -> [Char] -> Maybe [[Char]]
matchRegex Regex
re [Char]
rest of
            Just [[Char]
width, [Char]
precision, [Char]
typ, [Char]
rest, [Char]
_] ->
                (if [Char]
width forall a. Eq a => a -> a -> Bool
== [Char]
"*" then [Char]
"*" else [Char]
"") forall a. [a] -> [a] -> [a]
++
                (if [Char]
precision forall a. Eq a => a -> a -> Bool
== [Char]
"*" then [Char]
"*" else [Char]
"") forall a. [a] -> [a] -> [a]
++
                [Char]
typ forall a. [a] -> [a] -> [a]
++ [Char] -> [Char]
getFormats [Char]
rest
            Maybe [[Char]]
Nothing -> forall a. Int -> [a] -> [a]
take Int
1 [Char]
rest forall a. [a] -> [a] -> [a]
++ [Char] -> [Char]
getFormats [Char]
rest
      where
        -- constructed based on specifications in "man printf"
        re :: Regex
re = [Char] -> Regex
mkRegex [Char]
"#?-?\\+? ?0?(\\*|\\d*)\\.?(\\d*|\\*)([diouxXfFeEgGaAcsbq])((\n|.)*)"
        --            \____ _____/\___ ____/   \____ ____/\_________ _________/ \______ /
        --                 V          V             V               V               V
        --               flags    field width  precision   format character        rest
        -- field width and precision can be specified with an '*' instead of a digit,
        -- in which case printf will accept one more argument for each '*' used


prop_checkUuoeCmd1 :: Bool
prop_checkUuoeCmd1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo $(date)"
prop_checkUuoeCmd2 :: Bool
prop_checkUuoeCmd2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo `date`"
prop_checkUuoeCmd3 :: Bool
prop_checkUuoeCmd3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo \"$(date)\""
prop_checkUuoeCmd4 :: Bool
prop_checkUuoeCmd4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo \"`date`\""
prop_checkUuoeCmd5 :: Bool
prop_checkUuoeCmd5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUuoeCmd [Char]
"echo \"The time is $(date)\""
prop_checkUuoeCmd6 :: Bool
prop_checkUuoeCmd6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUuoeCmd [Char]
"echo \"$(<file)\""
checkUuoeCmd :: CommandCheck
checkUuoeCmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"echo") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
    msg :: Id -> m ()
msg Id
id = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style Id
id Code
2005 [Char]
"Useless echo? Instead of 'echo $(cmd)', just use 'cmd'."
    f :: [Token] -> f ()
f [Token
token] = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
tokenIsJustCommandOutput Token
token) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
msg (Token -> Id
getId Token
token)
    f [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSetAssignment1 :: Bool
prop_checkSetAssignment1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSetAssignment [Char]
"set foo 42"
prop_checkSetAssignment2 :: Bool
prop_checkSetAssignment2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSetAssignment [Char]
"set foo = 42"
prop_checkSetAssignment3 :: Bool
prop_checkSetAssignment3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSetAssignment [Char]
"set foo=42"
prop_checkSetAssignment4 :: Bool
prop_checkSetAssignment4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSetAssignment [Char]
"set -- if=/dev/null"
prop_checkSetAssignment5 :: Bool
prop_checkSetAssignment5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSetAssignment [Char]
"set 'a=5'"
prop_checkSetAssignment6 :: Bool
prop_checkSetAssignment6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSetAssignment [Char]
"set"
checkSetAssignment :: CommandCheck
checkSetAssignment = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"set") (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f 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 (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
rest) Bool -> Bool -> Bool
&& [Char] -> Bool
isVariableName [Char]
str) Bool -> Bool -> Bool
|| forall {t :: * -> *}. Foldable t => t Char -> Bool
isAssignment [Char]
str =
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
var) Code
2121 [Char]
"To assign a variable, use just 'var=value', no 'set ..'."
      where str :: [Char]
str = Token -> [Char]
literal Token
var
    f [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isAssignment :: t Char -> Bool
isAssignment t Char
str = Char
'=' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t Char
str
    literal :: Token -> [Char]
literal (T_NormalWord Id
_ [Token]
l) = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Char]
literal [Token]
l
    literal (T_Literal Id
_ [Char]
str) = [Char]
str
    literal Token
_ = [Char]
"*"


prop_checkExportedExpansions1 :: Bool
prop_checkExportedExpansions1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExportedExpansions [Char]
"export $foo"
prop_checkExportedExpansions2 :: Bool
prop_checkExportedExpansions2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkExportedExpansions [Char]
"export \"$foo\""
prop_checkExportedExpansions3 :: Bool
prop_checkExportedExpansions3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExportedExpansions [Char]
"export foo"
prop_checkExportedExpansions4 :: Bool
prop_checkExportedExpansions4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkExportedExpansions [Char]
"export ${foo?}"
checkExportedExpansions :: CommandCheck
checkExportedExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"export") (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> m ()
check Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
name <- Token -> Maybe [Char]
getSingleUnmodifiedBracedString Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2163 forall a b. (a -> b) -> a -> b
$
            [Char]
"This does not export '" forall a. [a] -> [a] -> [a]
++ [Char]
name forall a. [a] -> [a] -> [a]
++ [Char]
"'. Remove $/${} for that, or use ${var?} to quiet."

prop_checkReadExpansions1 :: Bool
prop_checkReadExpansions1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read $var"
prop_checkReadExpansions2 :: Bool
prop_checkReadExpansions2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read -r $var"
prop_checkReadExpansions3 :: Bool
prop_checkReadExpansions3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read -p $var"
prop_checkReadExpansions4 :: Bool
prop_checkReadExpansions4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read -rd $delim name"
prop_checkReadExpansions5 :: Bool
prop_checkReadExpansions5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read \"$var\""
prop_checkReadExpansions6 :: Bool
prop_checkReadExpansions6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read -a $var"
prop_checkReadExpansions7 :: Bool
prop_checkReadExpansions7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read $1"
prop_checkReadExpansions8 :: Bool
prop_checkReadExpansions8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read ${var?}"
prop_checkReadExpansions9 :: Bool
prop_checkReadExpansions9 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read arr[val]"
checkReadExpansions :: CommandCheck
checkReadExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"read") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check
  where
    options :: [Token] -> Maybe [([Char], (Token, Token))]
options = [Char] -> [Token] -> Maybe [([Char], (Token, Token))]
getGnuOpts [Char]
flagsForRead
    getVars :: Token -> [Token]
getVars Token
cmd = forall a. a -> Maybe a -> a
fromMaybe [] forall a b. (a -> b) -> a -> b
$ do
        [([Char], (Token, Token))]
opts <- [Token] -> Maybe [([Char], (Token, Token))]
options forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd
        forall (m :: * -> *) a. Monad m => a -> m a
return [Token
y | ([Char]
x,(Token
_, Token
y)) <- [([Char], (Token, Token))]
opts, forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
x Bool -> Bool -> Bool
|| [Char]
x forall a. Eq a => a -> a -> Bool
== [Char]
"a"]

    check :: Token -> m ()
check Token
cmd = do
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
dollarWarning forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getVars Token
cmd
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
arrayWarning forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd

    dollarWarning :: Token -> m ()
dollarWarning Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
name <- Token -> Maybe [Char]
getSingleUnmodifiedBracedString Token
t
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char] -> Bool
isVariableName [Char]
name   -- e.g. not $1
        forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2229 forall a b. (a -> b) -> a -> b
$
            [Char]
"This does not read '" forall a. [a] -> [a] -> [a]
++ [Char]
name forall a. [a] -> [a] -> [a]
++ [Char]
"'. Remove $/${} for that, or use ${var?} to quiet."

    arrayWarning :: Token -> f ()
arrayWarning Token
word =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isUnquotedBracket forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
word) Code
2313 forall a b. (a -> b) -> a -> b
$
                [Char]
"Quote array indices to avoid them expanding as globs."

    isUnquotedBracket :: Token -> Bool
isUnquotedBracket Token
t =
        case Token
t of
            T_Glob Id
_ (Char
'[':[Char]
_) -> Bool
True
            Token
_ -> Bool
False

-- Return the single variable expansion that makes up this word, if any.
-- e.g. $foo -> $foo, "$foo"'' -> $foo , "hello $name" -> Nothing
getSingleUnmodifiedBracedString :: Token -> Maybe String
getSingleUnmodifiedBracedString :: Token -> Maybe [Char]
getSingleUnmodifiedBracedString Token
word =
    case Token -> [Token]
getWordParts Token
word of
        [T_DollarBraced Id
_ Bool
_ Token
l] ->
            let contents :: [Char]
contents = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Char]]
oversimplify Token
l
                name :: [Char]
name = [Char] -> [Char]
getBracedReference [Char]
contents
            in forall (f :: * -> *). Alternative f => Bool -> f ()
guard ([Char]
contents forall a. Eq a => a -> a -> Bool
== [Char]
name) forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
contents
        [Token]
_ -> forall a. Maybe a
Nothing

prop_checkAliasesUsesArgs1 :: Bool
prop_checkAliasesUsesArgs1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkAliasesUsesArgs [Char]
"alias a='cp $1 /a'"
prop_checkAliasesUsesArgs2 :: Bool
prop_checkAliasesUsesArgs2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkAliasesUsesArgs [Char]
"alias $1='foo'"
prop_checkAliasesUsesArgs3 :: Bool
prop_checkAliasesUsesArgs3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkAliasesUsesArgs [Char]
"alias a=\"echo \\${@}\""
checkAliasesUsesArgs :: CommandCheck
checkAliasesUsesArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"alias") ([Token] -> Analysis
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    re :: Regex
re = [Char] -> Regex
mkRegex [Char]
"\\$\\{?[0-9*@]"
    f :: [Token] -> Analysis
f = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArg
    checkArg :: Token -> f ()
checkArg Token
arg =
        let string :: [Char]
string = [Char] -> Token -> [Char]
getLiteralStringDef [Char]
"_" Token
arg in
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'=' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
string Bool -> Bool -> Bool
&& [Char]
string [Char] -> Regex -> Bool
`matches` Regex
re) forall a b. (a -> b) -> a -> b
$
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
arg) Code
2142
                    [Char]
"Aliases can't use positional parameters. Use a function."


prop_checkAliasesExpandEarly1 :: Bool
prop_checkAliasesExpandEarly1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkAliasesExpandEarly [Char]
"alias foo=\"echo $PWD\""
prop_checkAliasesExpandEarly2 :: Bool
prop_checkAliasesExpandEarly2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkAliasesExpandEarly [Char]
"alias -p"
prop_checkAliasesExpandEarly3 :: Bool
prop_checkAliasesExpandEarly3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkAliasesExpandEarly [Char]
"alias foo='echo {1..10}'"
checkAliasesExpandEarly :: CommandCheck
checkAliasesExpandEarly = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"alias") ([Token] -> Analysis
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> Analysis
f = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArg
    checkArg :: Token -> m ()
checkArg Token
arg | Char
'=' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [[Char]]
oversimplify Token
arg) =
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isLiteral) forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
arg) forall a b. (a -> b) -> a -> b
$
            \Token
x -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
x) Code
2139 [Char]
"This expands when defined, not when used. Consider escaping."
    checkArg Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnsetGlobs1 :: Bool
prop_checkUnsetGlobs1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnsetGlobs [Char]
"unset foo[1]"
prop_checkUnsetGlobs2 :: Bool
prop_checkUnsetGlobs2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnsetGlobs [Char]
"unset foo"
prop_checkUnsetGlobs3 :: Bool
prop_checkUnsetGlobs3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnsetGlobs [Char]
"unset foo[$i]"
prop_checkUnsetGlobs4 :: Bool
prop_checkUnsetGlobs4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnsetGlobs [Char]
"unset foo[x${i}y]"
prop_checkUnsetGlobs5 :: Bool
prop_checkUnsetGlobs5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnsetGlobs [Char]
"unset foo]["
checkUnsetGlobs :: CommandCheck
checkUnsetGlobs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"unset") (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> f ()
check Token
arg =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
arg) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
arg) Code
2184 [Char]
"Quote arguments to unset so they're not glob expanded."


prop_checkFindWithoutPath1 :: Bool
prop_checkFindWithoutPath1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindWithoutPath [Char]
"find -type f"
prop_checkFindWithoutPath2 :: Bool
prop_checkFindWithoutPath2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindWithoutPath [Char]
"find"
prop_checkFindWithoutPath3 :: Bool
prop_checkFindWithoutPath3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindWithoutPath [Char]
"find . -type f"
prop_checkFindWithoutPath4 :: Bool
prop_checkFindWithoutPath4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindWithoutPath [Char]
"find -H -L \"$path\" -print"
prop_checkFindWithoutPath5 :: Bool
prop_checkFindWithoutPath5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindWithoutPath [Char]
"find -O3 ."
prop_checkFindWithoutPath6 :: Bool
prop_checkFindWithoutPath6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindWithoutPath [Char]
"find -D exec ."
prop_checkFindWithoutPath7 :: Bool
prop_checkFindWithoutPath7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindWithoutPath [Char]
"find --help"
prop_checkFindWithoutPath8 :: Bool
prop_checkFindWithoutPath8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindWithoutPath [Char]
"find -Hx . -print"
checkFindWithoutPath :: CommandCheck
checkFindWithoutPath = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> f ()
f t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
args)) =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
t Token -> [Char] -> Bool
`hasFlag` [Char]
"help" Bool -> Bool -> Bool
|| [Token] -> Bool
hasPath [Token]
args) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
cmd) Code
2185 [Char]
"Some finds don't have a default path. Specify '.' explicitly."

    -- This is a bit of a kludge. find supports flag arguments both before and
    -- after the path, as well as multiple non-flag arguments that are not the
    -- path. We assume that all the pre-path flags are single characters from a
    -- list of GNU and macOS flags.
    hasPath :: [Token] -> Bool
hasPath (Token
first:[Token]
rest) =
        let flag :: [Char]
flag = [Char] -> Token -> [Char]
getLiteralStringDef [Char]
"___" Token
first in
            Bool -> Bool
not ([Char]
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
flag) Bool -> Bool -> Bool
|| forall {t :: * -> *}. Foldable t => t Char -> Bool
isLeadingFlag [Char]
flag Bool -> Bool -> Bool
&& [Token] -> Bool
hasPath [Token]
rest
    hasPath [] = Bool
False
    isLeadingFlag :: t Char -> Bool
isLeadingFlag t Char
flag = forall (t :: * -> *) a. Foldable t => t a -> Int
length t Char
flag forall a. Ord a => a -> a -> Bool
<= Int
2 Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
leadingFlagChars) t Char
flag
    leadingFlagChars :: [Char]
leadingFlagChars=[Char]
"-EHLPXdfsxO0123456789"


prop_checkTimeParameters1 :: Bool
prop_checkTimeParameters1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTimeParameters [Char]
"time -f lol sleep 10"
prop_checkTimeParameters2 :: Bool
prop_checkTimeParameters2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTimeParameters [Char]
"time sleep 10"
prop_checkTimeParameters3 :: Bool
prop_checkTimeParameters3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTimeParameters [Char]
"time -p foo"
prop_checkTimeParameters4 :: Bool
prop_checkTimeParameters4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTimeParameters [Char]
"command time -f lol sleep 10"
checkTimeParameters :: CommandCheck
checkTimeParameters = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"time") 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]
_)) =
        forall {m :: * -> *} {t :: * -> *}.
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash, Shell
Sh] forall a b. (a -> b) -> a -> b
$
            let s :: [Char]
s = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Char]]
oversimplify Token
args in
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s Bool -> Bool -> Bool
&& [Char]
s forall a. Eq a => a -> a -> Bool
/= [Char]
"-p") forall a b. (a -> b) -> a -> b
$
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
cmd) Code
2023 [Char]
"The shell may override 'time' as seen in man time(1). Use 'command time ..' for that one."

    f Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkTimedCommand1 :: Bool
prop_checkTimedCommand1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTimedCommand [Char]
"#!/bin/sh\ntime -p foo | bar"
prop_checkTimedCommand2 :: Bool
prop_checkTimedCommand2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTimedCommand [Char]
"#!/bin/dash\ntime ( foo; bar; )"
prop_checkTimedCommand3 :: Bool
prop_checkTimedCommand3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTimedCommand [Char]
"#!/bin/sh\ntime sleep 1"
checkTimedCommand :: CommandCheck
checkTimedCommand = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"time") 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]
_))) =
        forall {m :: * -> *} {t :: * -> *}.
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Dash] forall a b. (a -> b) -> a -> b
$ do
            let cmd :: Token
cmd = forall a. [a] -> a
last [Token]
args -- "time" is parsed with a command as argument
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isPiped Token
cmd) forall a b. (a -> b) -> a -> b
$
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
c) Code
2176 [Char]
"'time' is undefined for pipelines. time single stage or bash -c instead."
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall {m :: * -> *}. MonadFail m => Token -> m Bool
isSimple Token
cmd forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just Bool
False) forall a b. (a -> b) -> a -> b
$
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
cmd) Code
2177 [Char]
"'time' is undefined for compound commands, time sh -c instead."
    f Token
_ = 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]
_) -> forall (m :: * -> *) a. Monad m => a -> m a
return Token
a
            Token
_ -> forall (m :: * -> *) a. MonadFail m => [Char] -> m a
fail [Char]
""
    isSimple :: Token -> m Bool
isSimple Token
cmd = do
        Token
innerCommand <- forall {m :: * -> *}. MonadFail m => Token -> m Token
getCommand Token
cmd
        case Token
innerCommand of
            T_SimpleCommand {} -> forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False

prop_checkLocalScope1 :: Bool
prop_checkLocalScope1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkLocalScope [Char]
"local foo=3"
prop_checkLocalScope2 :: Bool
prop_checkLocalScope2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkLocalScope [Char]
"f() { local foo=3; }"
checkLocalScope :: CommandCheck
checkLocalScope = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"local") forall a b. (a -> b) -> a -> b
$ \Token
t ->
    forall {m :: * -> *} {t :: * -> *}.
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash, Shell
Dash] forall a b. (a -> b) -> a -> b
$ do -- Ksh allows it, Sh doesn't support local
        [Token]
path <- forall {m :: * -> *}.
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isFunctionLike [Token]
path) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2168 [Char]
"'local' is only valid in functions."

prop_checkMultipleDeclaring1 :: Bool
prop_checkMultipleDeclaring1 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"local") [Char]
"q() { local readonly var=1; }"
prop_checkMultipleDeclaring2 :: Bool
prop_checkMultipleDeclaring2 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"local") [Char]
"q() { local var=1; }"
prop_checkMultipleDeclaring3 :: Bool
prop_checkMultipleDeclaring3 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"readonly") [Char]
"readonly local foo=5"
prop_checkMultipleDeclaring4 :: Bool
prop_checkMultipleDeclaring4 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"export") [Char]
"export readonly foo=5"
prop_checkMultipleDeclaring5 :: Bool
prop_checkMultipleDeclaring5 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"local") [Char]
"f() { local -r foo=5; }"
prop_checkMultipleDeclaring6 :: Bool
prop_checkMultipleDeclaring6 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"declare") [Char]
"declare -rx foo=5"
prop_checkMultipleDeclaring7 :: Bool
prop_checkMultipleDeclaring7 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMultipleDeclaring [Char]
"readonly") [Char]
"readonly 'local' foo=5"
checkMultipleDeclaring :: [Char] -> CommandCheck
checkMultipleDeclaring [Char]
cmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
cmd) (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> m ()
check Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
lit <- Token -> Maybe [Char]
getUnquotedLiteral Token
t
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
lit forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
declaringCommands
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2316 forall a b. (a -> b) -> a -> b
$
                 [Char]
"This applies " forall a. [a] -> [a] -> [a]
++ [Char]
cmd forall a. [a] -> [a] -> [a]
++ [Char]
" to the variable named " forall a. [a] -> [a] -> [a]
++ [Char]
lit forall a. [a] -> [a] -> [a]
++
                 [Char]
", which is probably not what you want. Use a separate command or the appropriate `declare` options instead."

prop_checkDeprecatedTempfile1 :: Bool
prop_checkDeprecatedTempfile1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkDeprecatedTempfile [Char]
"var=$(tempfile)"
prop_checkDeprecatedTempfile2 :: Bool
prop_checkDeprecatedTempfile2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkDeprecatedTempfile [Char]
"tempfile=$(mktemp)"
checkDeprecatedTempfile :: CommandCheck
checkDeprecatedTempfile = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"tempfile") forall a b. (a -> b) -> a -> b
$
    \Token
t -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2186 [Char]
"tempfile is deprecated. Use mktemp instead."

prop_checkDeprecatedEgrep :: Bool
prop_checkDeprecatedEgrep = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkDeprecatedEgrep [Char]
"egrep '.+'"
checkDeprecatedEgrep :: CommandCheck
checkDeprecatedEgrep = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"egrep") forall a b. (a -> b) -> a -> b
$
    \Token
t -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2196 [Char]
"egrep is non-standard and deprecated. Use grep -E instead."

prop_checkDeprecatedFgrep :: Bool
prop_checkDeprecatedFgrep = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkDeprecatedFgrep [Char]
"fgrep '*' files"
checkDeprecatedFgrep :: CommandCheck
checkDeprecatedFgrep = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"fgrep") forall a b. (a -> b) -> a -> b
$
    \Token
t -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2197 [Char]
"fgrep is non-standard and deprecated. Use grep -F instead."

prop_checkWhileGetoptsCase1 :: Bool
prop_checkWhileGetoptsCase1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case $x in a) foo;; esac; done"
prop_checkWhileGetoptsCase2 :: Bool
prop_checkWhileGetoptsCase2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done"
prop_checkWhileGetoptsCase3 :: Bool
prop_checkWhileGetoptsCase3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done"
prop_checkWhileGetoptsCase4 :: Bool
prop_checkWhileGetoptsCase4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done"
prop_checkWhileGetoptsCase5 :: Bool
prop_checkWhileGetoptsCase5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done"
prop_checkWhileGetoptsCase6 :: Bool
prop_checkWhileGetoptsCase6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case $y in a) foo;; esac; done"
prop_checkWhileGetoptsCase7 :: Bool
prop_checkWhileGetoptsCase7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case x$x in xa) foo;; xb) foo;; esac; done"
prop_checkWhileGetoptsCase8 :: Bool
prop_checkWhileGetoptsCase8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do x=a; case $x in a) foo;; esac; done"
checkWhileGetoptsCase :: CommandCheck
checkWhileGetoptsCase = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"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 <- forall {m :: * -> *}.
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        Parameters
params <- forall r (m :: * -> *). MonadReader r m => m r
ask
        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            [Char]
options <- Token -> Maybe [Char]
getLiteralString Token
arg1
            [Char]
getoptsVar <- Token -> Maybe [Char]
getLiteralString Token
name
            (T_WhileExpression Id
_ [Token]
_ [Token]
body) <- 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])]
_) <- forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Token
findCase [Token]
body forall {a}. [a] -> Int -> Maybe a
!!! Int
0

            -- Make sure getopts name and case variable matches
            [T_DollarBraced Id
_ Bool
_ Token
bracedWord] <- forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
var
            [T_Literal Id
_ [Char]
caseVar] <- forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
bracedWord
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
caseVar forall a. Eq a => a -> a -> Bool
== [Char]
getoptsVar

            -- Make sure the variable isn't modified
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> [Char] -> Bool
modifiesVariable Parameters
params (Id -> [Token] -> Token
T_BraceGroup (Int -> Id
Id Int
0) [Token]
body) [Char]
getoptsVar

            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Id -> [[Char]] -> Token -> Analysis
check (Token -> Id
getId Token
arg1) (forall a b. (a -> b) -> [a] -> [b]
map (forall a. a -> [a] -> [a]
:[]) forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
/= Char
':') [Char]
options) Token
caseCmd
    f Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check :: Id -> [String] -> Token -> Analysis
    check :: Id -> [[Char]] -> Token -> Analysis
check Id
optId [[Char]]
opts (T_CaseExpression Id
id Token
_ [(CaseType, [Token], [Token])]
list) = do
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall a. Maybe a
Nothing forall k a. Ord k => k -> Map k a -> Bool
`M.member` Map (Maybe [Char]) Token
handledMap) forall a b. (a -> b) -> a -> b
$ do
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Id -> [Char] -> m ()
warnUnhandled Id
optId Id
id) forall a b. (a -> b) -> a -> b
$ forall a. [Maybe a] -> [a]
catMaybes forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [k]
M.keys Map (Maybe [Char]) ()
notHandled

                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall k a. Ord k => k -> Map k a -> Bool
`M.member` Map (Maybe [Char]) Token
handledMap) [forall a. a -> Maybe a
Just [Char]
"*",forall a. a -> Maybe a
Just [Char]
"?"]) forall a b. (a -> b) -> a -> b
$
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2220 [Char]
"Invalid flags are not handled. Add a *) case."

            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
(Maybe [Char], Token) -> m ()
warnRedundant forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [(k, a)]
M.toList Map (Maybe [Char]) Token
notRequested

        where
            handledMap :: Map (Maybe [Char]) Token
handledMap = forall k a. Ord k => [(k, a)] -> Map k a
M.fromList (forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall {a} {c}. (a, [Token], c) -> [(Maybe [Char], Token)]
getHandledStrings [(CaseType, [Token], [Token])]
list)
            requestedMap :: Map (Maybe [Char]) ()
requestedMap = forall k a. Ord k => [(k, a)] -> Map k a
M.fromList forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (\[Char]
x -> (forall a. a -> Maybe a
Just [Char]
x, ())) [[Char]]
opts

            notHandled :: Map (Maybe [Char]) ()
notHandled = forall k a b. Ord k => Map k a -> Map k b -> Map k a
M.difference Map (Maybe [Char]) ()
requestedMap Map (Maybe [Char]) Token
handledMap
            notRequested :: Map (Maybe [Char]) Token
notRequested = forall k a b. Ord k => Map k a -> Map k b -> Map k a
M.difference Map (Maybe [Char]) Token
handledMap Map (Maybe [Char]) ()
requestedMap

    warnUnhandled :: p -> Id -> [Char] -> m ()
warnUnhandled p
optId Id
caseId [Char]
str =
        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
caseId Code
2213 forall a b. (a -> b) -> a -> b
$ [Char]
"getopts specified -" forall a. [a] -> [a] -> [a]
++ ([Char] -> [Char]
e4m [Char]
str) forall a. [a] -> [a] -> [a]
++ [Char]
", but it's not handled by this 'case'."

    warnRedundant :: (Maybe [Char], Token) -> m ()
warnRedundant (Just [Char]
str, Token
expr)
        | [Char]
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [[Char]
"*", [Char]
":", [Char]
"?"] =
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
expr) Code
2214 [Char]
"This case is not specified by getopts."
    warnRedundant (Maybe [Char], Token)
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    getHandledStrings :: (a, [Token], c) -> [(Maybe [Char], Token)]
getHandledStrings (a
_, [Token]
globs, c
_) =
        forall a b. (a -> b) -> [a] -> [b]
map (\Token
x -> (Token -> Maybe [Char]
literal Token
x, Token
x)) [Token]
globs

    literal :: Token -> Maybe String
    literal :: Token -> Maybe [Char]
literal Token
t = do
        Token -> Maybe [Char]
getLiteralString Token
t forall a. Semigroup a => a -> a -> a
<> Token -> Maybe [Char]
fromGlob Token
t

    fromGlob :: Token -> Maybe [Char]
fromGlob Token
t =
        case Token
t of
            T_Glob Id
_ [Char
'[', Char
c, Char
']'] -> forall (m :: * -> *) a. Monad m => a -> m a
return [Char
c]
            T_Glob Id
_ [Char]
"*" -> forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"*"
            T_Glob Id
_ [Char]
"?" -> forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
"?"
            Token
_ -> forall a. Maybe a
Nothing

    whileLoop :: Token -> Maybe Bool
whileLoop Token
t =
        case Token
t of
            T_WhileExpression {} -> forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Script {} -> forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            Token
_ -> 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 {}) -> forall (m :: * -> *) a. Monad m => a -> m a
return Token
x
            Token
_ -> forall a. Maybe a
Nothing

prop_checkCatastrophicRm1 :: Bool
prop_checkCatastrophicRm1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r $1/$2"
prop_checkCatastrophicRm2 :: Bool
prop_checkCatastrophicRm2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r /home/$foo"
prop_checkCatastrophicRm3 :: Bool
prop_checkCatastrophicRm3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r /home/${USER:?}/*"
prop_checkCatastrophicRm4 :: Bool
prop_checkCatastrophicRm4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -fr /home/$(whoami)/*"
prop_checkCatastrophicRm5 :: Bool
prop_checkCatastrophicRm5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r /home/${USER:-thing}/*"
prop_checkCatastrophicRm6 :: Bool
prop_checkCatastrophicRm6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm --recursive /etc/*$config*"
prop_checkCatastrophicRm8 :: Bool
prop_checkCatastrophicRm8 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -rf /home"
prop_checkCatastrophicRm10 :: Bool
prop_checkCatastrophicRm10 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}"
prop_checkCatastrophicRm11 :: Bool
prop_checkCatastrophicRm11 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r /{bin,sbin}/$exec"
prop_checkCatastrophicRm12 :: Bool
prop_checkCatastrophicRm12 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r /{{usr,},{bin,sbin}}/$exec"
prop_checkCatastrophicRm13 :: Bool
prop_checkCatastrophicRm13 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r /{{a,b},{c,d}}/$exec"
prop_checkCatastrophicRmA :: Bool
prop_checkCatastrophicRmA = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -rf /usr /lib/nvidia-current/xorg/xorg"
prop_checkCatastrophicRmB :: Bool
prop_checkCatastrophicRmB = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -rf \"$STEAMROOT/\"*"
checkCatastrophicRm :: CommandCheck
checkCatastrophicRm = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"rm") forall a b. (a -> b) -> a -> b
$ \Token
t ->
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isRecursive Token
t) forall a b. (a -> b) -> a -> b
$
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkWord forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
braceExpand) forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
  where
    isRecursive :: Token -> Bool
isRecursive = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"r", [Char]
"R", [Char]
"recursive"]) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, [Char])]
getAllFlags

    checkWord :: Token -> f ()
checkWord Token
token =
        case Token -> Maybe [Char]
getLiteralString Token
token of
            Just [Char]
str ->
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall {t :: * -> *}. Foldable t => t Char -> [Char]
fixPath [Char]
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
importantPaths) forall a b. (a -> b) -> a -> b
$
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
token) Code
2114 [Char]
"Warning: deletes a system directory."
            Maybe [Char]
Nothing ->
                forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkWord' Token
token

    checkWord' :: Token -> m ()
checkWord' Token
token = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
filename <- Token -> Maybe [Char]
getPotentialPath Token
token
        let path :: [Char]
path = forall {t :: * -> *}. Foldable t => t Char -> [Char]
fixPath [Char]
filename
        forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
path forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
importantPaths) forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
token) Code
2115 forall a b. (a -> b) -> a -> b
$ [Char]
"Use \"${var:?}\" to ensure this never expands to " forall a. [a] -> [a] -> [a]
++ [Char]
path forall a. [a] -> [a] -> [a]
++ [Char]
" ."

    fixPath :: t Char -> [Char]
fixPath t Char
filename =
        let normalized :: [Char]
normalized = forall {t :: * -> *} {a}. (Foldable t, Eq a) => a -> t a -> [a]
skipRepeating Char
'/' forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {t :: * -> *} {a}. (Foldable t, Eq a) => a -> t a -> [a]
skipRepeating Char
'*' forall a b. (a -> b) -> a -> b
$ t Char
filename in
            if [Char]
normalized forall a. Eq a => a -> a -> Bool
== [Char]
"/" then [Char]
normalized else forall {a}. Eq a => a -> [a] -> [a]
stripTrailing Char
'/' [Char]
normalized

    getPotentialPath :: Token -> Maybe [Char]
getPotentialPath = forall (m :: * -> *).
Monad m =>
(Token -> m [Char]) -> Token -> m [Char]
getLiteralStringExt Token -> Maybe [Char]
f
      where
        f :: Token -> Maybe [Char]
f (T_Glob Id
_ [Char]
str) = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
str
        f (T_DollarBraced Id
_ Bool
_ Token
word) =
            let var :: [Char]
var = Token -> [Char]
onlyLiteralString Token
word in
                -- This shouldn't handle non-colon cases.
                if forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` [Char]
var) [[Char]
":?", [Char]
":-", [Char]
":="]
                then forall a. Maybe a
Nothing
                else forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
""
        f Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return [Char]
""

    stripTrailing :: a -> [a] -> [a]
stripTrailing a
c = forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall a. Eq a => a -> a -> Bool
== a
c) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse
    skipRepeating :: a -> t a -> [a]
skipRepeating a
c = 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 forall a. a -> [a] -> [a]
: case [a]
r of a
b:[a]
rest | a
b forall a. Eq a => a -> a -> Bool
== a
c Bool -> Bool -> Bool
&& a
a forall a. Eq a => a -> a -> Bool
== a
b -> [a]
rest; [a]
_ -> [a]
r

    paths :: [[Char]]
paths = [
        [Char]
"", [Char]
"/bin", [Char]
"/etc", [Char]
"/home", [Char]
"/mnt", [Char]
"/usr", [Char]
"/usr/share", [Char]
"/usr/local",
        [Char]
"/var", [Char]
"/lib", [Char]
"/dev", [Char]
"/media", [Char]
"/boot", [Char]
"/lib64", [Char]
"/usr/bin"
        ]
    importantPaths :: [[Char]]
importantPaths = forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null) forall a b. (a -> b) -> a -> b
$
        [[Char]
"", [Char]
"/", [Char]
"/*", [Char]
"/*/*"] forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\[Char]
x -> forall a b. (a -> b) -> [a] -> [b]
map (forall a. [a] -> [a] -> [a]
++[Char]
x) [[Char]]
paths)


prop_checkLetUsage1 :: Bool
prop_checkLetUsage1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkLetUsage [Char]
"let a=1"
prop_checkLetUsage2 :: Bool
prop_checkLetUsage2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkLetUsage [Char]
"(( a=1 ))"
checkLetUsage :: CommandCheck
checkLetUsage = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"let") forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall {m :: * -> *} {t :: * -> *}.
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash,Shell
Ksh] forall a b. (a -> b) -> a -> b
$ do
        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style (Token -> Id
getId Token
t) Code
2219 forall a b. (a -> b) -> a -> b
$ [Char]
"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
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Bool
hasTarget Bool -> Bool -> Bool
|| Token -> Bool
mayBecomeMultipleArgs Token
single) forall a b. (a -> b) -> a -> b
$
                Token -> f ()
handler Token
token
        [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    args :: [(Token, [Char])]
args = Token -> [(Token, [Char])]
getAllFlags Token
token
    params :: [Token]
params = [Token
x | (Token
x,[Char]
"") <- [(Token, [Char])]
args]
    hasTarget :: Bool
hasTarget =
        forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\(Token
_,[Char]
x) -> [Char]
x forall a. Eq a => a -> a -> Bool
/= [Char]
"" Bool -> Bool -> Bool
&& [Char]
x forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
"target-directory") [(Token, [Char])]
args

prop_checkMvArguments1 :: Bool
prop_checkMvArguments1 = CommandCheck -> [Char] -> Bool
verify    CommandCheck
checkMvArguments [Char]
"mv 'foo bar'"
prop_checkMvArguments2 :: Bool
prop_checkMvArguments2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv foo bar"
prop_checkMvArguments3 :: Bool
prop_checkMvArguments3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv 'foo bar'{,bak}"
prop_checkMvArguments4 :: Bool
prop_checkMvArguments4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv \"$@\""
prop_checkMvArguments5 :: Bool
prop_checkMvArguments5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv -t foo bar"
prop_checkMvArguments6 :: Bool
prop_checkMvArguments6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv --target-directory=foo bar"
prop_checkMvArguments7 :: Bool
prop_checkMvArguments7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv --target-direc=foo bar"
prop_checkMvArguments8 :: Bool
prop_checkMvArguments8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv --version"
prop_checkMvArguments9 :: Bool
prop_checkMvArguments9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkMvArguments [Char]
"mv \"${!var}\""
checkMvArguments :: CommandCheck
checkMvArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"mv") forall a b. (a -> b) -> a -> b
$ forall {f :: * -> *}. Monad f => (Token -> f ()) -> Token -> f ()
missingDestination forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
t) Code
2224 [Char]
"This mv has no destination. Check the arguments."

checkCpArguments :: CommandCheck
checkCpArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"cp") forall a b. (a -> b) -> a -> b
$ forall {f :: * -> *}. Monad f => (Token -> f ()) -> Token -> f ()
missingDestination forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
t) Code
2225 [Char]
"This cp has no destination. Check the arguments."

checkLnArguments :: CommandCheck
checkLnArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"ln") forall a b. (a -> b) -> a -> b
$ forall {f :: * -> *}. Monad f => (Token -> f ()) -> Token -> f ()
missingDestination forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2226 [Char]
"This ln has no destination. Check the arguments, or specify '.' explicitly."


prop_checkFindRedirections1 :: Bool
prop_checkFindRedirections1 = CommandCheck -> [Char] -> Bool
verify    CommandCheck
checkFindRedirections [Char]
"find . -exec echo {} > file \\;"
prop_checkFindRedirections2 :: Bool
prop_checkFindRedirections2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindRedirections [Char]
"find . -exec echo {} \\; > file"
prop_checkFindRedirections3 :: Bool
prop_checkFindRedirections3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindRedirections [Char]
"find . -execdir sh -c 'foo > file' \\;"
checkFindRedirections :: CommandCheck
checkFindRedirections = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = do
        Maybe Token
redirecting <- 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
                -- This assumes IDs are sequential, which is mostly but not always true.
                let minRedir :: Id
minRedir = forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
redirs
                let maxArg :: Id
maxArg   = forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
args
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Id
minRedir forall a. Ord a => a -> a -> Bool
< Id
maxArg) forall a b. (a -> b) -> a -> b
$
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
minRedir Code
2227
                        [Char]
"Redirection applies to the find command itself. Rewrite to work per action (or move to end)."
            Maybe Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkWhich :: Bool
prop_checkWhich = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkWhich [Char]
"which '.+'"
checkWhich :: CommandCheck
checkWhich = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"which") forall a b. (a -> b) -> a -> b
$
    \Token
t -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2230 [Char]
"'which' is non-standard. Use builtin 'command -v' instead."

prop_checkSudoRedirect1 :: Bool
prop_checkSudoRedirect1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSudoRedirect [Char]
"sudo echo 3 > /proc/file"
prop_checkSudoRedirect2 :: Bool
prop_checkSudoRedirect2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSudoRedirect [Char]
"sudo cmd < input"
prop_checkSudoRedirect3 :: Bool
prop_checkSudoRedirect3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSudoRedirect [Char]
"sudo cmd >> file"
prop_checkSudoRedirect4 :: Bool
prop_checkSudoRedirect4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSudoRedirect [Char]
"sudo cmd &> file"
prop_checkSudoRedirect5 :: Bool
prop_checkSudoRedirect5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoRedirect [Char]
"sudo cmd 2>&1"
prop_checkSudoRedirect6 :: Bool
prop_checkSudoRedirect6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoRedirect [Char]
"sudo cmd 2> log"
prop_checkSudoRedirect7 :: Bool
prop_checkSudoRedirect7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoRedirect [Char]
"sudo cmd > /dev/null 2>&1"
checkSudoRedirect :: CommandCheck
checkSudoRedirect = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"sudo") forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = do
        Maybe Token
t_redir <- 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
_) ->
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
warnAbout [Token]
redirs
    warnAbout :: Token -> m ()
warnAbout (T_FdRedirect Id
_ [Char]
s (T_IoFile Id
id Token
op Token
file))
        | (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
s Bool -> Bool -> Bool
|| [Char]
s forall a. Eq a => a -> a -> Bool
== [Char]
"&") Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
special Token
file) =
        case Token
op of
            T_Less Id
_ ->
              forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
op) Code
2024
                [Char]
"sudo doesn't affect redirects. Use sudo cat file | .."
            T_Greater Id
_ ->
              forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
op) Code
2024
                [Char]
"sudo doesn't affect redirects. Use ..| sudo tee file"
            T_DGREAT Id
_ ->
              forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
op) Code
2024
                [Char]
"sudo doesn't affect redirects. Use .. | sudo tee -a file"
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    warnAbout Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    special :: Token -> Bool
special Token
file = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [[Char]]
oversimplify Token
file) forall a. Eq a => a -> a -> Bool
== [Char]
"/dev/null"

prop_checkSudoArgs1 :: Bool
prop_checkSudoArgs1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSudoArgs [Char]
"sudo cd /root"
prop_checkSudoArgs2 :: Bool
prop_checkSudoArgs2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSudoArgs [Char]
"sudo export x=3"
prop_checkSudoArgs3 :: Bool
prop_checkSudoArgs3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoArgs [Char]
"sudo ls /usr/local/protected"
prop_checkSudoArgs4 :: Bool
prop_checkSudoArgs4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoArgs [Char]
"sudo ls && export x=3"
prop_checkSudoArgs5 :: Bool
prop_checkSudoArgs5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoArgs [Char]
"sudo echo ls"
prop_checkSudoArgs6 :: Bool
prop_checkSudoArgs6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoArgs [Char]
"sudo -n -u export ls"
prop_checkSudoArgs7 :: Bool
prop_checkSudoArgs7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSudoArgs [Char]
"sudo docker export foo"
checkSudoArgs :: CommandCheck
checkSudoArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"sudo") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [([Char], (Token, Token))]
opts <- [Token] -> Maybe [([Char], (Token, Token))]
parseOpts forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
        let nonFlags :: [Token]
nonFlags = [Token
x | ([Char]
"",(Token
x, Token
_)) <- [([Char], (Token, Token))]
opts]
        Token
commandArg <- [Token]
nonFlags forall {a}. [a] -> Int -> Maybe a
!!! Int
0
        [Char]
command <- Token -> Maybe [Char]
getLiteralString Token
commandArg
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
command forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
builtins
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2232 forall a b. (a -> b) -> a -> b
$ [Char]
"Can't use sudo with builtins like " forall a. [a] -> [a] -> [a]
++ [Char]
command forall a. [a] -> [a] -> [a]
++ [Char]
". Did you want sudo sh -c .. instead?"
    builtins :: [[Char]]
builtins = [ [Char]
"cd", [Char]
"eval", [Char]
"export", [Char]
"history", [Char]
"read", [Char]
"source", [Char]
"wait" ]
    -- This mess is why ShellCheck prefers not to know.
    parseOpts :: [Token] -> Maybe [([Char], (Token, Token))]
parseOpts = [Char] -> [Token] -> Maybe [([Char], (Token, Token))]
getBsdOpts [Char]
"vAknSbEHPa:g:h:p:u:c:T:r:"

prop_checkSourceArgs1 :: Bool
prop_checkSourceArgs1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSourceArgs [Char]
"#!/bin/sh\n. script arg"
prop_checkSourceArgs2 :: Bool
prop_checkSourceArgs2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSourceArgs [Char]
"#!/bin/sh\n. script"
prop_checkSourceArgs3 :: Bool
prop_checkSourceArgs3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSourceArgs [Char]
"#!/bin/bash\n. script arg"
checkSourceArgs :: CommandCheck
checkSourceArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
".") forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall {m :: * -> *} {t :: * -> *}.
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Dash] forall a b. (a -> b) -> a -> b
$
        case Token -> [Token]
arguments Token
t of
            (Token
file:Token
arg1:[Token]
_) -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
arg1) Code
2240 forall a b. (a -> b) -> a -> b
$
                [Char]
"The dot command does not support arguments in sh/dash. Set them as variables."
            [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkChmodDashr1 :: Bool
prop_checkChmodDashr1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkChmodDashr [Char]
"chmod -r 0755 dir"
prop_checkChmodDashr2 :: Bool
prop_checkChmodDashr2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkChmodDashr [Char]
"chmod -R 0755 dir"
prop_checkChmodDashr3 :: Bool
prop_checkChmodDashr3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkChmodDashr [Char]
"chmod a-r dir"
checkChmodDashr :: CommandCheck
checkChmodDashr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"chmod") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
    check :: Token -> m ()
check Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
flag <- Token -> Maybe [Char]
getLiteralString Token
t
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
flag forall a. Eq a => a -> a -> Bool
== [Char]
"-r"
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2253 [Char]
"Use -R to recurse, or explicitly a-r to remove read permissions."

prop_checkXargsDashi1 :: Bool
prop_checkXargsDashi1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkXargsDashi [Char]
"xargs -i{} echo {}"
prop_checkXargsDashi2 :: Bool
prop_checkXargsDashi2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkXargsDashi [Char]
"xargs -I{} echo {}"
prop_checkXargsDashi3 :: Bool
prop_checkXargsDashi3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkXargsDashi [Char]
"xargs sed -i -e foo"
prop_checkXargsDashi4 :: Bool
prop_checkXargsDashi4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkXargsDashi [Char]
"xargs -e sed -i foo"
prop_checkXargsDashi5 :: Bool
prop_checkXargsDashi5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkXargsDashi [Char]
"xargs -x sed -i foo"
checkXargsDashi :: CommandCheck
checkXargsDashi = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"xargs") forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [([Char], (Token, Token))]
opts <- [Token] -> Maybe [([Char], (Token, Token))]
parseOpts forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
        (Token
option, Token
value) <- forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup [Char]
"i" [([Char], (Token, Token))]
opts
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
option) Code
2267 [Char]
"GNU xargs -i is deprecated in favor of -I{}"
    parseOpts :: [Token] -> Maybe [([Char], (Token, Token))]
parseOpts = [Char] -> [Token] -> Maybe [([Char], (Token, Token))]
getBsdOpts [Char]
"0oprtxadR:S:J:L:l:n:P:s:e:E:i:I:"


prop_checkArgComparison1 :: Bool
prop_checkArgComparison1 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare a = b"
prop_checkArgComparison2 :: Bool
prop_checkArgComparison2 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare a =b"
prop_checkArgComparison3 :: Bool
prop_checkArgComparison3 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare a=b"
prop_checkArgComparison4 :: Bool
prop_checkArgComparison4 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"export") [Char]
"export a +=b"
prop_checkArgComparison7 :: Bool
prop_checkArgComparison7 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare -a +i foo"
prop_checkArgComparison8 :: Bool
prop_checkArgComparison8 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"let") [Char]
"let x = 0"
prop_checkArgComparison9 :: Bool
prop_checkArgComparison9 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"alias") [Char]
"alias x =0"
-- This mirrors checkSecondArgIsComparison but for arguments to local/readonly/declare/export
checkArgComparison :: [Char] -> CommandCheck
checkArgComparison [Char]
cmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
cmd) forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
wordsWithEqual
  where
    wordsWithEqual :: Token -> m ()
wordsWithEqual Token
t = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
    check :: Token -> m ()
check Token
arg = do
      forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        [Char]
str <- Token -> Maybe [Char]
getLeadingUnquotedString Token
arg
        case [Char]
str of
            Char
'=':[Char]
_ ->
                forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
headId Token
arg) Code
2290 forall a b. (a -> b) -> a -> b
$
                    [Char]
"Remove spaces around = to assign."
            Char
'+':Char
'=':[Char]
_ ->
                forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
headId Token
arg) Code
2290 forall a b. (a -> b) -> a -> b
$
                    [Char]
"Remove spaces around += to append."
            [Char]
_ -> forall a. Maybe a
Nothing

       -- 'let' is parsed as a sequence of arithmetic expansions,
       -- so we want the additional warning for "x="
      forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
cmd forall a. Eq a => a -> a -> Bool
== [Char]
"let") forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        Token
token <- Token -> Maybe Token
getTrailingUnquotedLiteral Token
arg
        [Char]
str <- Token -> Maybe [Char]
getLiteralString Token
token
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ [Char]
"=" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` [Char]
str
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
token) Code
2290 forall a b. (a -> b) -> a -> b
$
            [Char]
"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 -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"local") [Char]
"f() { local a=$(false); }"
prop_checkMaskedReturns2 :: Bool
prop_checkMaskedReturns2 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"declare") [Char]
"declare a=$(false)"
prop_checkMaskedReturns3 :: Bool
prop_checkMaskedReturns3 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"declare") [Char]
"declare a=\"`false`\""
prop_checkMaskedReturns4 :: Bool
prop_checkMaskedReturns4 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"readonly") [Char]
"readonly a=$(false)"
prop_checkMaskedReturns5 :: Bool
prop_checkMaskedReturns5 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"readonly") [Char]
"readonly a=\"`false`\""
prop_checkMaskedReturns6 :: Bool
prop_checkMaskedReturns6 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMaskedReturns [Char]
"declare") [Char]
"declare a; a=$(false)"
prop_checkMaskedReturns7 :: Bool
prop_checkMaskedReturns7 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMaskedReturns [Char]
"local") [Char]
"f() { local -r a=$(false); }"
prop_checkMaskedReturns8 :: Bool
prop_checkMaskedReturns8 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMaskedReturns [Char]
"readonly") [Char]
"a=$(false); readonly a"
prop_checkMaskedReturns9 :: Bool
prop_checkMaskedReturns9 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"typeset") [Char]
"#!/bin/ksh\n f() { typeset -r x=$(false); }"
prop_checkMaskedReturns10 :: Bool
prop_checkMaskedReturns10 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMaskedReturns [Char]
"typeset") [Char]
"#!/bin/ksh\n function f { typeset -r x=$(false); }"
prop_checkMaskedReturns11 :: Bool
prop_checkMaskedReturns11 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkMaskedReturns [Char]
"typeset") [Char]
"#!/bin/bash\n f() { typeset -r x=$(false); }"
prop_checkMaskedReturns12 :: Bool
prop_checkMaskedReturns12 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"typeset") [Char]
"typeset -r x=$(false);"
prop_checkMaskedReturns13 :: Bool
prop_checkMaskedReturns13 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"typeset") [Char]
"f() { typeset -g x=$(false); }"
prop_checkMaskedReturns14 :: Bool
prop_checkMaskedReturns14 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"declare") [Char]
"declare x=${ false; }"
prop_checkMaskedReturns15 :: Bool
prop_checkMaskedReturns15 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkMaskedReturns [Char]
"declare") [Char]
"f() { declare x=$(false); }"
checkMaskedReturns :: [Char] -> CommandCheck
checkMaskedReturns [Char]
str = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
str) forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
checkCmd
  where
    checkCmd :: Token -> m ()
checkCmd Token
t = do
        [Token]
path <- forall {m :: * -> *}.
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        Shell
shell <- forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Shell
shellType
        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            [Char]
name <- Token -> Maybe [Char]
getCommandName Token
t

            let flags :: [[Char]]
flags = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd (Token -> [(Token, [Char])]
getAllFlags Token
t)
            let hasDashR :: Bool
hasDashR =  [Char]
"r" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
flags
            let hasDashG :: Bool
hasDashG =  [Char]
"g" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
flags
            let isInScopedFunction :: Bool
isInScopedFunction = 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
&& [Char] -> Bool
isLocalInFunction [Char]
name Bool -> Bool -> Bool
&& Bool
isInScopedFunction
            let isReadOnly :: Bool
isReadOnly = [Char]
name forall a. Eq a => a -> a -> Bool
== [Char]
"readonly" Bool -> Bool -> Bool
|| Bool
hasDashR

            -- Don't warn about local variables that are declared readonly,
            -- because the workaround `local x; x=$(false); local -r x;` is annoying
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Bool
isLocal Bool -> Bool -> Bool
&& Bool
isReadOnly

            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArgs forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t

    checkArgs :: Token -> m ()
checkArgs (T_Assignment Id
id AssignmentMode
_ [Char]
_ [Token]
_ Token
word) | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
hasReturn forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word =
        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2155 [Char]
"Declare and assign separately to avoid masking return values."
    checkArgs Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isLocalInFunction :: [Char] -> Bool
isLocalInFunction = (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"local", [Char]
"declare", [Char]
"typeset"])
    isScopedFunction :: Shell -> Token -> Bool
isScopedFunction Shell
shell Token
t =
        case Token
t of
            T_BatsTest {} -> Bool
True
            -- In ksh, only functions declared with 'function' have their own scope
            T_Function Id
_ (FunctionKeyword Bool
hasFunction) FunctionParentheses
_ [Char]
_ Token
_ -> Shell
shell 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 -> [Char] -> Bool
verify CommandCheck
checkUnquotedEchoSpaces [Char]
"echo foo         bar"
prop_checkUnquotedEchoSpaces2 :: Bool
prop_checkUnquotedEchoSpaces2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo       foo"
prop_checkUnquotedEchoSpaces3 :: Bool
prop_checkUnquotedEchoSpaces3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo foo  bar"
prop_checkUnquotedEchoSpaces4 :: Bool
prop_checkUnquotedEchoSpaces4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo 'foo          bar'"
prop_checkUnquotedEchoSpaces5 :: Bool
prop_checkUnquotedEchoSpaces5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo a > myfile.txt b"
prop_checkUnquotedEchoSpaces6 :: Bool
prop_checkUnquotedEchoSpaces6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"        echo foo\\\n        bar"
checkUnquotedEchoSpaces :: CommandCheck
checkUnquotedEchoSpaces = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"echo") 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 <- forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Map Id (Position, Position)
tokenPositions
        Maybe Token
redir <- forall {m :: * -> *}.
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            let positions :: [(Position, Position)]
positions = forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Token
c -> forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (Token -> Id
getId Token
c) Map Id (Position, Position)
m) [Token]
args
            let pairs :: [((Position, Position), (Position, Position))]
pairs = forall a b. [a] -> [b] -> [(a, b)]
zip [(Position, Position)]
positions (forall a. Int -> [a] -> [a]
drop Int
1 [(Position, Position)]
positions)
            (T_Redirecting Id
_ [Token]
redirTokens Token
_) <- Maybe Token
redir
            let redirPositions :: [Position]
redirPositions = forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Token
c -> forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (Token -> Id
getId Token
c) Map Id (Position, Position)
m) [Token]
redirTokens
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall {t :: * -> *}.
Foldable t =>
t Position -> ((Position, Position), (Position, Position)) -> Bool
hasSpacesBetween [Position]
redirPositions) [((Position, Position), (Position, Position))]
pairs
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
t) Code
2291 [Char]
"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 forall a. Eq a => a -> a -> Bool
== Position -> Code
posLine Position
d
        Bool -> Bool -> Bool
&& ((Position -> Code
posColumn Position
c) forall a. Num a => a -> a -> a
- (Position -> Code
posColumn Position
b)) forall a. Ord a => a -> a -> Bool
>= Code
4
        Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Position
x -> Position
b forall a. Ord a => a -> a -> Bool
< Position
x Bool -> Bool -> Bool
&& Position
x forall a. Ord a => a -> a -> Bool
< Position
c) t Position
redirs)


prop_checkEvalArray1 :: Bool
prop_checkEvalArray1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkEvalArray  [Char]
"eval $@"
prop_checkEvalArray2 :: Bool
prop_checkEvalArray2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkEvalArray  [Char]
"eval \"${args[@]}\""
prop_checkEvalArray3 :: Bool
prop_checkEvalArray3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkEvalArray  [Char]
"eval \"${args[@]@Q}\""
prop_checkEvalArray4 :: Bool
prop_checkEvalArray4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkEvalArray  [Char]
"eval \"${args[*]@Q}\""
prop_checkEvalArray5 :: Bool
prop_checkEvalArray5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkEvalArray  [Char]
"eval \"$*\""
checkEvalArray :: CommandCheck
checkEvalArray = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"eval") (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
getWordParts forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> f ()
check Token
t =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isArrayExpansion Token
t) forall a b. (a -> b) -> a -> b
$
            if Token -> Bool
isEscaped Token
t
            then forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style (Token -> Id
getId Token
t) Code
2293 [Char]
"When eval'ing @Q-quoted words, use * rather than @ as the index."
            else forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2294 [Char]
"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
            -- Match ${arr[@]@Q} and ${@@Q} and such
            T_DollarBraced Id
_ Bool
_ Token
l -> Char
'Q' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char] -> [Char]
getBracedModifier (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Char]]
oversimplify Token
l)
            Token
_ -> Bool
False


prop_checkBackreferencingDeclaration1 :: Bool
prop_checkBackreferencingDeclaration1 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"declare") [Char]
"declare x=1 y=foo$x"
prop_checkBackreferencingDeclaration2 :: Bool
prop_checkBackreferencingDeclaration2 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"readonly") [Char]
"readonly x=1 y=$((1+x))"
prop_checkBackreferencingDeclaration3 :: Bool
prop_checkBackreferencingDeclaration3 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"local") [Char]
"local x=1 y=$(echo $x)"
prop_checkBackreferencingDeclaration4 :: Bool
prop_checkBackreferencingDeclaration4 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"local") [Char]
"local x=1 y[$x]=z"
prop_checkBackreferencingDeclaration5 :: Bool
prop_checkBackreferencingDeclaration5 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"declare") [Char]
"declare x=var $x=1"
prop_checkBackreferencingDeclaration6 :: Bool
prop_checkBackreferencingDeclaration6 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"declare") [Char]
"declare x=var $x=1"
prop_checkBackreferencingDeclaration7 :: Bool
prop_checkBackreferencingDeclaration7 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
"declare") [Char]
"declare x=var $k=$x"
checkBackreferencingDeclaration :: [Char] -> CommandCheck
checkBackreferencingDeclaration [Char]
cmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
cmd) forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
check
  where
    check :: Token -> m ()
check Token
t = forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m ()
foldM_ forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Map [Char] Id -> Token -> m (Map [Char] Id)
perArg forall k a. Map k a
M.empty forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t

    perArg :: Map [Char] Id -> Token -> m (Map [Char] Id)
perArg Map [Char] Id
leftArgs Token
t =
        case Token
t of
            T_Assignment Id
id AssignmentMode
_ [Char]
name [Token]
idx Token
t -> do
                forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Map [Char] Id -> [Token] -> m [()]
warnIfBackreferencing Map [Char] Id
leftArgs forall a b. (a -> b) -> a -> b
$ Token
tforall a. a -> [a] -> [a]
:[Token]
idx
                forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert [Char]
name Id
id Map [Char] Id
leftArgs
            Token
t -> do
                forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Map [Char] Id -> [Token] -> m [()]
warnIfBackreferencing Map [Char] Id
leftArgs [Token
t]
                forall (m :: * -> *) a. Monad m => a -> m a
return Map [Char] Id
leftArgs

    warnIfBackreferencing :: Map [Char] Id -> [Token] -> m [()]
warnIfBackreferencing Map [Char] Id
backrefs [Token]
l = do
        Map [Char] Id
references <- forall {m :: * -> *}.
MonadReader Parameters m =>
[Token] -> m (Map [Char] Id)
findReferences [Token]
l
        let reused :: Map [Char] Id
reused = forall k a b. Ord k => Map k a -> Map k b -> Map k a
M.intersection Map [Char] Id
backrefs Map [Char] Id
references
        forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM forall {m :: * -> *} {a}.
MonadWriter [TokenComment] m =>
(a, Id) -> m ()
msg forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [(k, a)]
M.toList Map [Char] Id
reused

    msg :: (a, Id) -> m ()
msg (a
name, Id
id) = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2318 forall a b. (a -> b) -> a -> b
$ [Char]
"This assignment is used again in this '" forall a. [a] -> [a] -> [a]
++ [Char]
cmd forall a. [a] -> [a] -> [a]
++ [Char]
"', but won't have taken effect. Use two '" forall a. [a] -> [a] -> [a]
++ [Char]
cmd forall a. [a] -> [a] -> [a]
++ [Char]
"'s."

    findReferences :: [Token] -> m (Map [Char] Id)
findReferences [Token]
list = do
        CFGAnalysis
cfga <- forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> CFGAnalysis
cfgAnalysis
        let graph :: CFGraph
graph = CFGAnalysis -> CFGraph
CF.graph CFGAnalysis
cfga
        let nodesMap :: Map Id (Set Int)
nodesMap = CFGAnalysis -> Map Id (Set Int)
CF.tokenToNodes CFGAnalysis
cfga
        let nodes :: Set Int
nodes = forall (f :: * -> *) a. (Foldable f, Ord a) => f (Set a) -> Set a
S.unions forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (\Id
id -> forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault forall a. Set a
S.empty Id
id Map Id (Set Int)
nodesMap) forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId forall a b. (a -> b) -> a -> b
$ [Token]
list
        let labels :: [CFNode]
labels = forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (forall (gr :: * -> * -> *) a b.
Graph gr =>
gr a b -> Int -> Maybe a
G.lab CFGraph
graph) forall a b. (a -> b) -> a -> b
$ forall a. Set a -> [a]
S.toList Set Int
nodes
        let references :: Map [Char] Id
references = forall k a. Ord k => [(k, a)] -> Map k a
M.fromList forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap CFNode -> [([Char], Id)]
refFromLabel [CFNode]
labels
        forall (m :: * -> *) a. Monad m => a -> m a
return Map [Char] Id
references

    refFromLabel :: CFNode -> [([Char], Id)]
refFromLabel CFNode
lab =
        case CFNode
lab of
            CFApplyEffects [IdTagged CFEffect]
effects -> forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe IdTagged CFEffect -> Maybe ([Char], Id)
refFromEffect [IdTagged CFEffect]
effects
            CFNode
_ -> []
    refFromEffect :: IdTagged CFEffect -> Maybe ([Char], Id)
refFromEffect IdTagged CFEffect
e =
        case IdTagged CFEffect
e of
            IdTagged Id
id (CFReadVariable [Char]
name) -> forall (m :: * -> *) a. Monad m => a -> m a
return ([Char]
name, Id
id)
            IdTagged CFEffect
_ -> forall a. Maybe a
Nothing


return []
runTests :: IO Bool
runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])