{-
    Copyright 2012-2021 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.Data
import ShellCheck.Interface
import ShellCheck.Parser
import ShellCheck.Regex

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

data CommandName = Exactly String | Basename String
    deriving (CommandName -> CommandName -> Bool
(CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool) -> Eq CommandName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: CommandName -> CommandName -> Bool
$c/= :: CommandName -> CommandName -> Bool
== :: CommandName -> CommandName -> Bool
$c== :: CommandName -> CommandName -> Bool
Eq, Eq CommandName
Eq CommandName
-> (CommandName -> CommandName -> Ordering)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> CommandName)
-> (CommandName -> CommandName -> CommandName)
-> Ord CommandName
CommandName -> CommandName -> Bool
CommandName -> CommandName -> Ordering
CommandName -> CommandName -> CommandName
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: CommandName -> CommandName -> CommandName
$cmin :: CommandName -> CommandName -> CommandName
max :: CommandName -> CommandName -> CommandName
$cmax :: CommandName -> CommandName -> CommandName
>= :: CommandName -> CommandName -> Bool
$c>= :: CommandName -> CommandName -> Bool
> :: CommandName -> CommandName -> Bool
$c> :: CommandName -> CommandName -> Bool
<= :: CommandName -> CommandName -> Bool
$c<= :: CommandName -> CommandName -> Bool
< :: CommandName -> CommandName -> Bool
$c< :: CommandName -> CommandName -> Bool
compare :: CommandName -> CommandName -> Ordering
$ccompare :: CommandName -> CommandName -> Ordering
$cp1Ord :: Eq CommandName
Ord)

data CommandCheck =
    CommandCheck CommandName (Token -> Analysis)


verify :: CommandCheck -> String -> Bool
verify :: CommandCheck -> String -> Bool
verify CommandCheck
f String
s = Checker -> String -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
verifyNot :: CommandCheck -> String -> Bool
verifyNot CommandCheck
f String
s = Checker -> String -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

commandChecks :: [CommandCheck]
commandChecks :: [CommandCheck]
commandChecks = [
    CommandCheck
checkTr
    ,CommandCheck
checkFindNameGlob
    ,CommandCheck
checkExpr
    ,CommandCheck
checkGrepRe
    ,CommandCheck
checkTrapQuotes
    ,CommandCheck
checkReturn
    ,CommandCheck
checkExit
    ,CommandCheck
checkFindExecWithSingleArgument
    ,CommandCheck
checkUnusedEchoEscapes
    ,CommandCheck
checkInjectableFindSh
    ,CommandCheck
checkFindActionPrecedence
    ,CommandCheck
checkMkdirDashPM
    ,CommandCheck
checkNonportableSignals
    ,CommandCheck
checkInteractiveSu
    ,CommandCheck
checkSshCommandString
    ,CommandCheck
checkPrintfVar
    ,CommandCheck
checkUuoeCmd
    ,CommandCheck
checkSetAssignment
    ,CommandCheck
checkExportedExpansions
    ,CommandCheck
checkAliasesUsesArgs
    ,CommandCheck
checkAliasesExpandEarly
    ,CommandCheck
checkUnsetGlobs
    ,CommandCheck
checkFindWithoutPath
    ,CommandCheck
checkTimeParameters
    ,CommandCheck
checkTimedCommand
    ,CommandCheck
checkLocalScope
    ,CommandCheck
checkDeprecatedTempfile
    ,CommandCheck
checkDeprecatedEgrep
    ,CommandCheck
checkDeprecatedFgrep
    ,CommandCheck
checkWhileGetoptsCase
    ,CommandCheck
checkCatastrophicRm
    ,CommandCheck
checkLetUsage
    ,CommandCheck
checkMvArguments, CommandCheck
checkCpArguments, CommandCheck
checkLnArguments
    ,CommandCheck
checkFindRedirections
    ,CommandCheck
checkReadExpansions
    ,CommandCheck
checkSudoRedirect
    ,CommandCheck
checkSudoArgs
    ,CommandCheck
checkSourceArgs
    ,CommandCheck
checkChmodDashr
    ,CommandCheck
checkXargsDashi
    ,CommandCheck
checkUnquotedEchoSpaces
    ,CommandCheck
checkEvalArray
    ]
    [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ (String -> CommandCheck) -> [String] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map String -> CommandCheck
checkArgComparison [String]
declaringCommands
    [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ (String -> CommandCheck) -> [String] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map String -> CommandCheck
checkMaskedReturns [String]
declaringCommands


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

prop_verifyOptionalExamples :: Bool
prop_verifyOptionalExamples = ((CheckDescription, CommandCheck) -> Bool)
-> [(CheckDescription, CommandCheck)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (CheckDescription, CommandCheck) -> Bool
check [(CheckDescription, CommandCheck)]
optionalCommandChecks
  where
    check :: (CheckDescription, CommandCheck) -> Bool
check (CheckDescription
desc, CommandCheck
check) =
      CommandCheck -> String -> Bool
verify CommandCheck
check (CheckDescription -> String
cdPositive CheckDescription
desc)
      Bool -> Bool -> Bool
&& CommandCheck -> String -> Bool
verifyNot CommandCheck
check (CheckDescription -> String
cdNegative CheckDescription
desc)

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

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

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

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

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

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

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


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


checkCommand :: Map.Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand :: Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand Map CommandName (Token -> Analysis)
map t :: Token
t@(T_SimpleCommand Id
id [Token]
cmdPrefix (Token
cmd:[Token]
rest)) = Maybe Analysis -> Analysis
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe Analysis -> Analysis) -> Maybe Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
    String
name <- Token -> Maybe String
getLiteralString Token
cmd
    Analysis -> Maybe Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (Analysis -> Maybe Analysis) -> Analysis -> Maybe Analysis
forall a b. (a -> b) -> a -> b
$
        if Char
'/' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
name
        then
            (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Basename (String -> CommandName) -> String -> CommandName
forall a b. (a -> b) -> a -> b
$ String -> String
basename String
name) Map CommandName (Token -> Analysis)
map Token
t
        else if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"builtin" Bool -> Bool -> Bool
&& Bool -> Bool
not ([Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
rest) then
            let t' :: Token
t' = Id -> [Token] -> [Token] -> Token
T_SimpleCommand Id
id [Token]
cmdPrefix [Token]
rest
                selectedBuiltin :: String
selectedBuiltin = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString (Token -> Maybe String)
-> ([Token] -> Token) -> [Token] -> Maybe String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> Token
forall a. [a] -> a
head ([Token] -> Maybe String) -> [Token] -> Maybe String
forall a b. (a -> b) -> a -> b
$ [Token]
rest
            in (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Exactly String
selectedBuiltin) Map CommandName (Token -> Analysis)
map Token
t'
        else do
            (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Exactly String
name) Map CommandName (Token -> Analysis)
map Token
t
            (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Basename String
name) Map CommandName (Token -> Analysis)
map Token
t

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

getChecker :: [CommandCheck] -> Checker
getChecker :: [CommandCheck] -> Checker
getChecker [CommandCheck]
list = Checker :: (Root -> Analysis) -> (Token -> Analysis) -> Checker
Checker {
    perScript :: Root -> Analysis
perScript = Analysis -> Root -> Analysis
forall a b. a -> b -> a
const (Analysis -> Root -> Analysis) -> Analysis -> Root -> Analysis
forall a b. (a -> b) -> a -> b
$ () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (),
    perToken :: Token -> Analysis
perToken = Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand Map CommandName (Token -> Analysis)
map
    }
  where
    map :: Map CommandName (Token -> Analysis)
map = [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap [CommandCheck]
list


checker :: AnalysisSpec -> Parameters -> Checker
checker :: AnalysisSpec -> Parameters -> Checker
checker AnalysisSpec
spec Parameters
params = [CommandCheck] -> Checker
getChecker ([CommandCheck] -> Checker) -> [CommandCheck] -> Checker
forall a b. (a -> b) -> a -> b
$ [CommandCheck]
commandChecks [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ [CommandCheck]
optionals
  where
    keys :: [String]
keys = AnalysisSpec -> [String]
asOptionalChecks AnalysisSpec
spec
    optionals :: [CommandCheck]
optionals =
        if String
"all" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
keys
        then ((CheckDescription, CommandCheck) -> CommandCheck)
-> [(CheckDescription, CommandCheck)] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map (CheckDescription, CommandCheck) -> CommandCheck
forall a b. (a, b) -> b
snd [(CheckDescription, CommandCheck)]
optionalCommandChecks
        else (String -> Maybe CommandCheck) -> [String] -> [CommandCheck]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\String
x -> String -> Map String CommandCheck -> Maybe CommandCheck
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
x Map String CommandCheck
optionalCheckMap) [String]
keys

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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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


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

prop_checkExit1 :: Bool
prop_checkExit1 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit"
prop_checkExit2 :: Bool
prop_checkExit2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit 1"
prop_checkExit3 :: Bool
prop_checkExit3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit $var"
prop_checkExit4 :: Bool
prop_checkExit4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit String
"exit $((a|b))"
prop_checkExit5 :: Bool
prop_checkExit5 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit String
"exit -1"
prop_checkExit6 :: Bool
prop_checkExit6 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit String
"exit 1000"
prop_checkExit7 :: Bool
prop_checkExit7 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit String
"exit 'hello world'"
checkExit :: CommandCheck
checkExit = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"exit") ((Id -> Analysis) -> (Id -> Analysis) -> Token -> Analysis
forall (f :: * -> *).
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
        (\Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c Code
2241 String
"The exit status can only be one integer 0-255. Use stdout for other data.")
        (\Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c Code
2242 String
"Can only exit with status 0-255. Other data should be written to stdout/stderr."))

returnOrExit :: (Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit Id -> f ()
multi Id -> f ()
invalid = ([Token] -> f ()
f ([Token] -> f ()) -> (Token -> [Token]) -> Token -> f ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> f ()
f (Token
first:Token
second:[Token]
_) =
        Id -> f ()
multi (Token -> Id
getId Token
first)
    f [Token
value] =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isInvalid (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> String
literal Token
value) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> f ()
invalid (Token -> Id
getId Token
value)
    f [Token]
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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


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

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


prop_checkUnusedEchoEscapes1 :: Bool
prop_checkUnusedEchoEscapes1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnusedEchoEscapes String
"echo 'foo\\nbar\\n'"
prop_checkUnusedEchoEscapes2 :: Bool
prop_checkUnusedEchoEscapes2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes String
"echo -e 'foi\\nbar'"
prop_checkUnusedEchoEscapes3 :: Bool
prop_checkUnusedEchoEscapes3 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnusedEchoEscapes String
"echo \"n:\\t42\""
prop_checkUnusedEchoEscapes4 :: Bool
prop_checkUnusedEchoEscapes4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes String
"echo lol"
prop_checkUnusedEchoEscapes5 :: Bool
prop_checkUnusedEchoEscapes5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes String
"echo -n -e '\n'"
checkUnusedEchoEscapes :: CommandCheck
checkUnusedEchoEscapes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"echo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    hasEscapes :: Regex
hasEscapes = String -> Regex
mkRegex String
"\\\\[rnt]"
    f :: Token -> m ()
f Token
cmd =
        [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Bash, Shell
Ksh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
cmd Token -> String -> Bool
`hasFlag` String
"e") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
examine ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd

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


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

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

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


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


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


prop_checkNonportableSignals1 :: Bool
prop_checkNonportableSignals1 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f 8"
prop_checkNonportableSignals2 :: Bool
prop_checkNonportableSignals2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals String
"trap f 0"
prop_checkNonportableSignals3 :: Bool
prop_checkNonportableSignals3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals String
"trap f 14"
prop_checkNonportableSignals4 :: Bool
prop_checkNonportableSignals4 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f SIGKILL"
prop_checkNonportableSignals5 :: Bool
prop_checkNonportableSignals5 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f 9"
prop_checkNonportableSignals6 :: Bool
prop_checkNonportableSignals6 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals String
"trap f stop"
prop_checkNonportableSignals7 :: Bool
prop_checkNonportableSignals7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals String
"trap 'stop' int"
checkNonportableSignals :: CommandCheck
checkNonportableSignals = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"trap") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> m ()
f [Token]
args = case [Token]
args of
        Token
first:[Token]
rest | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isFlag Token
first -> (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check [Token]
rest
        [Token]
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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


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

    undirected :: Token -> Bool
undirected (T_Pipeline Id
_ [Token]
_ (Token
_:Token
_:[Token]
_)) = Bool
False
    -- 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 -> String -> Bool
verify CommandCheck
checkSshCommandString String
"ssh host \"echo $PS1\""
prop_checkSshCmdStr2 :: Bool
prop_checkSshCmdStr2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString String
"ssh host \"ls foo\""
prop_checkSshCmdStr3 :: Bool
prop_checkSshCmdStr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString String
"ssh \"$host\""
prop_checkSshCmdStr4 :: Bool
prop_checkSshCmdStr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString String
"ssh -i key \"$host\""
checkSshCommandString :: CommandCheck
checkSshCommandString = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"ssh") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    isOption :: Token -> Bool
isOption Token
x = String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
x)
    f :: [Token] -> m ()
f [Token]
args =
        case (Token -> Bool) -> [Token] -> ([Token], [Token])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Token -> Bool
isOption [Token]
args of
            ([], Token
hostport:r :: [Token]
r@(Token
_:[Token]
_)) -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg (Token -> m ()) -> Token -> m ()
forall a b. (a -> b) -> a -> b
$ [Token] -> Token
forall a. [a] -> a
last [Token]
r
            ([Token], [Token])
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkArg :: Token -> m ()
checkArg (T_NormalWord Id
_ [T_DoubleQuoted Id
id [Token]
parts]) =
        Maybe Token -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ ((Token -> Bool) -> [Token] -> Maybe Token
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isConstant) [Token]
parts) ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$
            \Token
x -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
x) Code
2029
                String
"Note that, unescaped, this expands on the client side."
    checkArg Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

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

    check :: Token -> t Token -> m ()
check Token
format t Token
more = do
        Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            String
string <- Token -> Maybe String
getLiteralString Token
format
            let formats :: String
formats = String -> String
getPrintfFormats String
string
            let formatCount :: Int
formatCount = String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
formats
            let argCount :: Int
argCount = t Token -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Token
more

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

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


prop_checkGetPrintfFormats1 :: Bool
prop_checkGetPrintfFormats1 = String -> String
getPrintfFormats String
"%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"s"
prop_checkGetPrintfFormats2 :: Bool
prop_checkGetPrintfFormats2 = String -> String
getPrintfFormats String
"%0*s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*s"
prop_checkGetPrintfFormats3 :: Bool
prop_checkGetPrintfFormats3 = String -> String
getPrintfFormats String
"%(%s)T" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"T"
prop_checkGetPrintfFormats4 :: Bool
prop_checkGetPrintfFormats4 = String -> String
getPrintfFormats String
"%d%%%(%s)T" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"dT"
prop_checkGetPrintfFormats5 :: Bool
prop_checkGetPrintfFormats5 = String -> String
getPrintfFormats String
"%bPassed: %d, %bFailed: %d%b, Skipped: %d, %bErrored: %d%b\\n" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"bdbdbdbdb"
prop_checkGetPrintfFormats6 :: Bool
prop_checkGetPrintfFormats6 = String -> String
getPrintfFormats String
"%s%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"ss"
prop_checkGetPrintfFormats7 :: Bool
prop_checkGetPrintfFormats7 = String -> String
getPrintfFormats String
"%s\n%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"ss"
getPrintfFormats :: String -> String
getPrintfFormats = String -> String
getFormats
  where
    -- 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 :: String -> String
getFormats String
string =
        case String
string of
            Char
'%':Char
'%':String
rest -> String -> String
getFormats String
rest
            Char
'%':Char
'(':String
rest ->
                case (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
')') String
rest of
                    Char
')':Char
c:String
trailing -> Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String -> String
getFormats String
trailing
                    String
_ -> String
""
            Char
'%':String
rest -> String -> String
regexBasedGetFormats String
rest
            Char
_:String
rest -> String -> String
getFormats String
rest
            [] -> String
""

    regexBasedGetFormats :: String -> String
regexBasedGetFormats String
rest =
        case Regex -> String -> Maybe [String]
matchRegex Regex
re String
rest of
            Just [String
width, String
precision, String
typ, String
rest, String
_] ->
                (if String
width String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*" then String
"*" else String
"") String -> String -> String
forall a. [a] -> [a] -> [a]
++
                (if String
precision String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*" then String
"*" else String
"") String -> String -> String
forall a. [a] -> [a] -> [a]
++
                String
typ String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
getFormats String
rest
            Maybe [String]
Nothing -> Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
1 String
rest String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
getFormats String
rest
      where
        -- constructed based on specifications in "man printf"
        re :: Regex
re = String -> Regex
mkRegex String
"#?-?\\+? ?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 a '*' instead of a digit,
        -- in which case printf will accept one more argument for each '*' used


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


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

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


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

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

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

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

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

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

-- 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 String
getSingleUnmodifiedBracedString Token
word =
    case Token -> [Token]
getWordParts Token
word of
        [T_DollarBraced Id
_ Bool
_ Token
l] ->
            let contents :: String
contents = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
                name :: String
name = String -> String
getBracedReference String
contents
            in Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (String
contents String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name) Maybe () -> Maybe String -> Maybe String
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
contents
        [Token]
_ -> Maybe String
forall a. Maybe a
Nothing

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


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


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


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

    -- 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 :: String
flag = String -> Token -> String
getLiteralStringDef String
"___" Token
first in
            Bool -> Bool
not (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
flag) Bool -> Bool -> Bool
|| String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
isLeadingFlag String
flag Bool -> Bool -> Bool
&& [Token] -> Bool
hasPath [Token]
rest
    hasPath [] = Bool
False
    isLeadingFlag :: t Char -> Bool
isLeadingFlag t Char
flag = t Char -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Char
flag Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
2 Bool -> Bool -> Bool
|| (Char -> Bool) -> t Char -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
leadingFlagChars) t Char
flag
    leadingFlagChars :: String
leadingFlagChars=String
"-EHLPXdfsxO0123456789"


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

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

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

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

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

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

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

prop_checkWhileGetoptsCase1 :: Bool
prop_checkWhileGetoptsCase1 = CommandCheck -> String -> Bool
verify CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case $x in a) foo;; esac; done"
prop_checkWhileGetoptsCase2 :: Bool
prop_checkWhileGetoptsCase2 = CommandCheck -> String -> Bool
verify CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done"
prop_checkWhileGetoptsCase3 :: Bool
prop_checkWhileGetoptsCase3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done"
prop_checkWhileGetoptsCase4 :: Bool
prop_checkWhileGetoptsCase4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done"
prop_checkWhileGetoptsCase5 :: Bool
prop_checkWhileGetoptsCase5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done"
prop_checkWhileGetoptsCase6 :: Bool
prop_checkWhileGetoptsCase6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case $y in a) foo;; esac; done"
prop_checkWhileGetoptsCase7 :: Bool
prop_checkWhileGetoptsCase7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do case x$x in xa) foo;; xb) foo;; esac; done"
prop_checkWhileGetoptsCase8 :: Bool
prop_checkWhileGetoptsCase8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase String
"while getopts 'a:b' x; do x=a; case $x in a) foo;; esac; done"
checkWhileGetoptsCase :: CommandCheck
checkWhileGetoptsCase = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"getopts") Token -> Analysis
f
  where
    f :: Token -> Analysis
    f :: Token -> Analysis
f t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:Token
arg1:Token
name:[Token]
_))  = do
        [Token]
path <- Token -> RWST Parameters [TokenComment] Cache Identity [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        Parameters
params <- RWST Parameters [TokenComment] Cache Identity Parameters
forall r (m :: * -> *). MonadReader r m => m r
ask
        Maybe Analysis -> Analysis
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe Analysis -> Analysis) -> Maybe Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
            String
options <- Token -> Maybe String
getLiteralString Token
arg1
            String
getoptsVar <- Token -> Maybe String
getLiteralString Token
name
            (T_WhileExpression Id
_ [Token]
_ [Token]
body) <- (Token -> Maybe Bool) -> [Token] -> Maybe Token
forall a. (a -> Maybe Bool) -> [a] -> Maybe a
findFirst Token -> Maybe Bool
whileLoop [Token]
path
            caseCmd :: Token
caseCmd@(T_CaseExpression Id
_ Token
var [(CaseType, [Token], [Token])]
_) <- (Token -> Maybe Token) -> [Token] -> [Token]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Token
findCase [Token]
body [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! Int
0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    findCase :: Token -> Maybe Token
findCase Token
t =
        case Token
t of
            T_Annotation Id
_ [Annotation]
_ Token
x -> Token -> Maybe Token
findCase Token
x
            T_Pipeline Id
_ [Token]
_ [Token
x] -> Token -> Maybe Token
findCase Token
x
            T_Redirecting Id
_ [Token]
_ x :: Token
x@(T_CaseExpression {}) -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
x
            Token
_ -> Maybe Token
forall a. Maybe a
Nothing

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

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

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

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

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

    stripTrailing :: a -> [a] -> [a]
stripTrailing a
c = [a] -> [a]
forall a. [a] -> [a]
reverse ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c) ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> [a]
forall a. [a] -> [a]
reverse
    skipRepeating :: a -> t a -> [a]
skipRepeating a
c = (a -> [a] -> [a]) -> [a] -> t a -> [a]
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr a -> [a] -> [a]
go []
      where
        go :: a -> [a] -> [a]
go a
a [a]
r = a
a a -> [a] -> [a]
forall a. a -> [a] -> [a]
: case [a]
r of a
b:[a]
rest | a
b a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c Bool -> Bool -> Bool
&& a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
b -> [a]
rest; [a]
_ -> [a]
r

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


prop_checkLetUsage1 :: Bool
prop_checkLetUsage1 = CommandCheck -> String -> Bool
verify CommandCheck
checkLetUsage String
"let a=1"
prop_checkLetUsage2 :: Bool
prop_checkLetUsage2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkLetUsage String
"(( a=1 ))"
checkLetUsage :: CommandCheck
checkLetUsage = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"let") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f Token
t = [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash,Shell
Ksh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2219 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Instead of 'let expr', prefer (( expr )) ."


missingDestination :: (Token -> f ()) -> Token -> f ()
missingDestination Token -> f ()
handler Token
token = do
    case [Token]
params of
        [Token
single] -> do
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Bool
hasTarget Bool -> Bool -> Bool
|| Token -> Bool
mayBecomeMultipleArgs Token
single) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Token -> f ()
handler Token
token
        [Token]
_ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    args :: [(Token, String)]
args = Token -> [(Token, String)]
getAllFlags Token
token
    params :: [Token]
params = [Token
x | (Token
x,String
"") <- [(Token, String)]
args]
    hasTarget :: Bool
hasTarget =
        ((Token, String) -> Bool) -> [(Token, String)] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\(Token
_,String
x) -> String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"" Bool -> Bool -> Bool
&& String
x String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
"target-directory") [(Token, String)]
args

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

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

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


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

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

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

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

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

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

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


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

       -- 'let' is parsed as a sequence of arithmetic expansions,
       -- so we want the additional warning for "x="
      Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
cmd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"let") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Token
token <- Token -> Maybe Token
getTrailingUnquotedLiteral Token
arg
        String
str <- Token -> Maybe String
getLiteralString Token
token
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
"=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
str
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2290 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
"Remove spaces around = to assign."

    headId :: Token -> Id
headId Token
t =
        case Token
t of
            T_NormalWord Id
_ (Token
x:[Token]
_) -> Token -> Id
getId Token
x
            Token
_ -> Token -> Id
getId Token
t


prop_checkMaskedReturns1 :: Bool
prop_checkMaskedReturns1 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"local") String
"f() { local a=$(false); }"
prop_checkMaskedReturns2 :: Bool
prop_checkMaskedReturns2 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare a=$(false)"
prop_checkMaskedReturns3 :: Bool
prop_checkMaskedReturns3 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare a=\"`false`\""
prop_checkMaskedReturns4 :: Bool
prop_checkMaskedReturns4 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"readonly") String
"readonly a=$(false)"
prop_checkMaskedReturns5 :: Bool
prop_checkMaskedReturns5 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"readonly") String
"readonly a=\"`false`\""
prop_checkMaskedReturns6 :: Bool
prop_checkMaskedReturns6 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare a; a=$(false)"
prop_checkMaskedReturns7 :: Bool
prop_checkMaskedReturns7 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"local") String
"f() { local -r a=$(false); }"
prop_checkMaskedReturns8 :: Bool
prop_checkMaskedReturns8 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"readonly") String
"a=$(false); readonly a"
prop_checkMaskedReturns9 :: Bool
prop_checkMaskedReturns9 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"#!/bin/ksh\n f() { typeset -r x=$(false); }"
prop_checkMaskedReturns10 :: Bool
prop_checkMaskedReturns10 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"#!/bin/ksh\n function f { typeset -r x=$(false); }"
prop_checkMaskedReturns11 :: Bool
prop_checkMaskedReturns11 = CommandCheck -> String -> Bool
verifyNot (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"#!/bin/bash\n f() { typeset -r x=$(false); }"
prop_checkMaskedReturns12 :: Bool
prop_checkMaskedReturns12 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"typeset -r x=$(false);"
prop_checkMaskedReturns13 :: Bool
prop_checkMaskedReturns13 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"typeset") String
"f() { typeset -g x=$(false); }"
prop_checkMaskedReturns14 :: Bool
prop_checkMaskedReturns14 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"declare x=${ false; }"
prop_checkMaskedReturns15 :: Bool
prop_checkMaskedReturns15 = CommandCheck -> String -> Bool
verify (String -> CommandCheck
checkMaskedReturns String
"declare") String
"f() { declare x=$(false); }"
checkMaskedReturns :: String -> CommandCheck
checkMaskedReturns String
str = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
str) Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
checkCmd
  where
    checkCmd :: Token -> m ()
checkCmd Token
t = do
        [Token]
path <- Token -> m [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        Shell
shell <- (Parameters -> Shell) -> m Shell
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Shell
shellType
        Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            String
name <- Token -> Maybe String
getCommandName Token
t

            let flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t)
            let hasDashR :: Bool
hasDashR =  String
"r" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
            let hasDashG :: Bool
hasDashG =  String
"g" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
            let isInScopedFunction :: Bool
isInScopedFunction = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Shell -> Token -> Bool
isScopedFunction Shell
shell) [Token]
path

            let isLocal :: Bool
isLocal = Bool -> Bool
not Bool
hasDashG Bool -> Bool -> Bool
&& String -> Bool
isLocalInFunction String
name Bool -> Bool -> Bool
&& Bool
isInScopedFunction
            let isReadOnly :: Bool
isReadOnly = String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"readonly" Bool -> Bool -> Bool
|| Bool
hasDashR

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

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

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

    isLocalInFunction :: String -> Bool
isLocalInFunction = (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"local", String
"declare", String
"typeset"])
    isScopedFunction :: Shell -> Token -> Bool
isScopedFunction Shell
shell Token
t =
        case Token
t of
            T_BatsTest {} -> Bool
True
            -- In ksh, only functions declared with 'function' have their own scope
            T_Function Id
_ (FunctionKeyword Bool
hasFunction) FunctionParentheses
_ String
_ Token
_ -> Shell
shell Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
/= Shell
Ksh Bool -> Bool -> Bool
|| Bool
hasFunction
            Token
_ -> Bool
False

    hasReturn :: Token -> Bool
hasReturn Token
t = case Token
t of
        T_Backticked {} -> Bool
True
        T_DollarExpansion {} -> Bool
True
        T_DollarBraceCommandExpansion {} -> Bool
True
        Token
_ -> Bool
False


prop_checkUnquotedEchoSpaces1 :: Bool
prop_checkUnquotedEchoSpaces1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnquotedEchoSpaces String
"echo foo         bar"
prop_checkUnquotedEchoSpaces2 :: Bool
prop_checkUnquotedEchoSpaces2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo       foo"
prop_checkUnquotedEchoSpaces3 :: Bool
prop_checkUnquotedEchoSpaces3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo foo  bar"
prop_checkUnquotedEchoSpaces4 :: Bool
prop_checkUnquotedEchoSpaces4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo 'foo          bar'"
prop_checkUnquotedEchoSpaces5 :: Bool
prop_checkUnquotedEchoSpaces5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"echo a > myfile.txt b"
prop_checkUnquotedEchoSpaces6 :: Bool
prop_checkUnquotedEchoSpaces6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces String
"        echo foo\\\n        bar"
checkUnquotedEchoSpaces :: CommandCheck
checkUnquotedEchoSpaces = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename String
"echo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
check
  where
    check :: Token -> m ()
check Token
t = do
        let args :: [Token]
args = Token -> [Token]
arguments Token
t
        Map Id (Position, Position)
m <- (Parameters -> Map Id (Position, Position))
-> m (Map Id (Position, Position))
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Map Id (Position, Position)
tokenPositions
        Maybe Token
redir <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
        Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            let positions :: [(Position, Position)]
positions = (Token -> Maybe (Position, Position))
-> [Token] -> [(Position, Position)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Token
c -> Id -> Map Id (Position, Position) -> Maybe (Position, Position)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
c) Map Id (Position, Position)
m) [Token]
args
            let pairs :: [((Position, Position), (Position, Position))]
pairs = [(Position, Position)]
-> [(Position, Position)]
-> [((Position, Position), (Position, Position))]
forall a b. [a] -> [b] -> [(a, b)]
zip [(Position, Position)]
positions (Int -> [(Position, Position)] -> [(Position, Position)]
forall a. Int -> [a] -> [a]
drop Int
1 [(Position, Position)]
positions)
            (T_Redirecting Id
_ [Token]
redirTokens Token
_) <- Maybe Token
redir
            let redirPositions :: [Position]
redirPositions = (Token -> Maybe Position) -> [Token] -> [Position]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Token
c -> (Position, Position) -> Position
forall a b. (a, b) -> a
fst ((Position, Position) -> Position)
-> Maybe (Position, Position) -> Maybe Position
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Id -> Map Id (Position, Position) -> Maybe (Position, Position)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
c) Map Id (Position, Position)
m) [Token]
redirTokens
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (((Position, Position), (Position, Position)) -> Bool)
-> [((Position, Position), (Position, Position))] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ([Position] -> ((Position, Position), (Position, Position)) -> Bool
forall (t :: * -> *).
Foldable t =>
t Position -> ((Position, Position), (Position, Position)) -> Bool
hasSpacesBetween [Position]
redirPositions) [((Position, Position), (Position, Position))]
pairs
            m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2291 String
"Quote repeated spaces to avoid them collapsing into one."

    hasSpacesBetween :: t Position -> ((Position, Position), (Position, Position)) -> Bool
hasSpacesBetween t Position
redirs ((Position
a,Position
b), (Position
c,Position
d)) =
        Position -> Code
posLine Position
a Code -> Code -> Bool
forall a. Eq a => a -> a -> Bool
== Position -> Code
posLine Position
d
        Bool -> Bool -> Bool
&& ((Position -> Code
posColumn Position
c) Code -> Code -> Code
forall a. Num a => a -> a -> a
- (Position -> Code
posColumn Position
b)) Code -> Code -> Bool
forall a. Ord a => a -> a -> Bool
>= Code
4
        Bool -> Bool -> Bool
&& Bool -> Bool
not ((Position -> Bool) -> t Position -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Position
x -> Position
b Position -> Position -> Bool
forall a. Ord a => a -> a -> Bool
< Position
x Bool -> Bool -> Bool
&& Position
x Position -> Position -> Bool
forall a. Ord a => a -> a -> Bool
< Position
c) t Position
redirs)


prop_checkEvalArray1 :: Bool
prop_checkEvalArray1 = CommandCheck -> String -> Bool
verify CommandCheck
checkEvalArray  String
"eval $@"
prop_checkEvalArray2 :: Bool
prop_checkEvalArray2 = CommandCheck -> String -> Bool
verify CommandCheck
checkEvalArray  String
"eval \"${args[@]}\""
prop_checkEvalArray3 :: Bool
prop_checkEvalArray3 = CommandCheck -> String -> Bool
verify CommandCheck
checkEvalArray  String
"eval \"${args[@]@Q}\""
prop_checkEvalArray4 :: Bool
prop_checkEvalArray4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkEvalArray  String
"eval \"${args[*]@Q}\""
prop_checkEvalArray5 :: Bool
prop_checkEvalArray5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkEvalArray  String
"eval \"$*\""
checkEvalArray :: CommandCheck
checkEvalArray = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly String
"eval") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token -> [Token]) -> [Token] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
getWordParts ([Token] -> [Token]) -> (Token -> [Token]) -> Token -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> f ()
check Token
t =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isArrayExpansion Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            if Token -> Bool
isEscaped Token
t
            then Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2293 String
"When eval'ing @Q-quoted words, use * rather than @ as the index."
            else Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2294 String
"eval negates the benefit of arrays. Drop eval to preserve whitespace/symbols (or eval as string)."

    isEscaped :: Token -> Bool
isEscaped Token
q =
        case Token
q of
            -- Match ${arr[@]@Q} and ${@@Q} and such
            T_DollarBraced Id
_ Bool
_ Token
l -> Char
'Q' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String -> String
getBracedModifier ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l)
            Token
_ -> Bool
False


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