{-
    Copyright 2012-2022 Vidar Holen

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

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

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

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

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

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

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

import Debug.Trace -- STRIP

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

data CommandCheck =
    CommandCheck CommandName (Token -> Analysis)


verify :: CommandCheck -> String -> Bool
verify :: CommandCheck -> [Char] -> Bool
verify CommandCheck
f [Char]
s = Checker -> [Char] -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) [Char]
s 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 -> [Char] -> Bool
verifyNot CommandCheck
f [Char]
s = Checker -> [Char] -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) [Char]
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]
++ ([Char] -> CommandCheck) -> [[Char]] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkArgComparison ([Char]
"alias" [Char] -> [[Char]] -> [[Char]]
forall a. a -> [a] -> [a]
: [[Char]]
declaringCommands)
    [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ ([Char] -> CommandCheck) -> [[Char]] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkMaskedReturns [[Char]]
declaringCommands
    [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ ([Char] -> CommandCheck) -> [[Char]] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkMultipleDeclaring [[Char]]
declaringCommands
    [CommandCheck] -> [CommandCheck] -> [CommandCheck]
forall a. [a] -> [a] -> [a]
++ ([Char] -> CommandCheck) -> [[Char]] -> [CommandCheck]
forall a b. (a -> b) -> [a] -> [b]
map [Char] -> CommandCheck
checkBackreferencingDeclaration [[Char]]
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 = "deprecate-which",
        cdDescription = "Suggest 'command -v' instead of 'which'",
        cdPositive = "which javac",
        cdNegative = "command -v javac"
    }, CommandCheck
checkWhich)
    ]
optionalCheckMap :: Map [Char] CommandCheck
optionalCheckMap = [([Char], CommandCheck)] -> Map [Char] CommandCheck
forall k a. Ord k => [(k, a)] -> Map k a
M.fromList ([([Char], CommandCheck)] -> Map [Char] CommandCheck)
-> [([Char], CommandCheck)] -> Map [Char] CommandCheck
forall a b. (a -> b) -> a -> b
$ ((CheckDescription, CommandCheck) -> ([Char], CommandCheck))
-> [(CheckDescription, CommandCheck)] -> [([Char], CommandCheck)]
forall a b. (a -> b) -> [a] -> [b]
map (\(CheckDescription
desc, CommandCheck
check) -> (CheckDescription -> [Char]
cdName CheckDescription
desc, CommandCheck
check)) [(CheckDescription, CommandCheck)]
optionalCommandChecks

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

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

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

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

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

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

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

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


buildCommandMap :: [CommandCheck] -> M.Map CommandName (Token -> Analysis)
buildCommandMap :: [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap = (Map CommandName (Token -> Analysis)
 -> CommandCheck -> Map CommandName (Token -> Analysis))
-> Map CommandName (Token -> Analysis)
-> [CommandCheck]
-> Map CommandName (Token -> Analysis)
forall b a. (b -> a -> b) -> b -> [a] -> b
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
M.empty
  where
    addCheck :: Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck Map CommandName (Token -> Analysis)
map (CommandCheck CommandName
name Token -> Analysis
function) =
        ((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
M.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 :: M.Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand :: Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand Map CommandName (Token -> Analysis)
map t :: Token
t@(T_SimpleCommand Id
id [Token]
cmdPrefix (Token
cmd:[Token]
rest)) = 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
    [Char]
name <- Token -> Maybe [Char]
getLiteralString Token
cmd
    Analysis -> Maybe Analysis
forall a. a -> Maybe a
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 -> [Char] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
name ->
               (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault Token -> Analysis
forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Basename ([Char] -> CommandName) -> [Char] -> CommandName
forall a b. (a -> b) -> a -> b
$ [Char] -> [Char]
basename [Char]
name) Map CommandName (Token -> Analysis)
map Token
t
           | [Char]
name [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"builtin", (Token
h:[Token]
_) <- [Token]
rest ->
               let t' :: Token
t' = Id -> [Token] -> [Token] -> Token
T_SimpleCommand Id
id [Token]
cmdPrefix [Token]
rest
                   selectedBuiltin :: [Char]
selectedBuiltin = Token -> [Char]
onlyLiteralString Token
h
               in (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault Token -> Analysis
forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Exactly [Char]
selectedBuiltin) Map CommandName (Token -> Analysis)
map Token
t'
           | Bool
otherwise -> do
               (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
M.findWithDefault Token -> Analysis
forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Exactly [Char]
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
M.findWithDefault Token -> Analysis
forall {b}. b -> Analysis
nullCheck ([Char] -> CommandName
Basename [Char]
name) Map CommandName (Token -> Analysis)
map Token
t

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

getChecker :: [CommandCheck] -> Checker
getChecker :: [CommandCheck] -> Checker
getChecker [CommandCheck]
list = 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 a. a -> RWST Parameters [TokenComment] Cache Identity a
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 :: [[Char]]
keys = AnalysisSpec -> [[Char]]
asOptionalChecks AnalysisSpec
spec
    optionals :: [CommandCheck]
optionals =
        if [Char]
"all" [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
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 ([Char] -> Maybe CommandCheck) -> [[Char]] -> [CommandCheck]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\[Char]
x -> [Char] -> Map [Char] CommandCheck -> Maybe CommandCheck
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup [Char]
x Map [Char] CommandCheck
optionalCheckMap) [[Char]]
keys

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

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

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


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

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

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

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

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

            (Token
first:[Token]
rest) -> do
                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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2306 [Char]
"Escape glob characters in arguments to expr to avoid pathname expansion."

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

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

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


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

checkGrepRe :: CommandCheck
checkGrepRe = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"grep") 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 :: [Char] -> Bool
skippable [Char]
s = Bool -> Bool
not ([Char]
"--regex=" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s) Bool -> Bool -> Bool
&& [Char]
"-" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [Char]
s
    f :: Token -> [Token] -> m ()
f Token
_ [] = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f Token
cmd (Token
x:[Token]
r) =
        let str :: [Char]
str = [Char] -> Token -> [Char]
getLiteralStringDef [Char]
"_" Token
x
        in
            if [Char]
str [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"--", [Char]
"-e", [Char]
"--regex"]
            then Token -> [Token] -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd [Token]
r -- Regex is *after* this
            else
                if [Char] -> Bool
skippable [Char]
str
                then Token -> [Token] -> m ()
f Token
cmd [Token]
r           -- Regex is elsewhere
                else 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 a. a -> m a
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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
re) Code
2062 [Char]
"Quote the grep pattern so the shell won't interpret it."

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

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

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


prop_checkTrapQuotes1 :: Bool
prop_checkTrapQuotes1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTrapQuotes [Char]
"trap \"echo $num\" INT"
prop_checkTrapQuotes1a :: Bool
prop_checkTrapQuotes1a = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTrapQuotes [Char]
"trap \"echo `ls`\" INT"
prop_checkTrapQuotes2 :: Bool
prop_checkTrapQuotes2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTrapQuotes [Char]
"trap 'echo $num' INT"
prop_checkTrapQuotes3 :: Bool
prop_checkTrapQuotes3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTrapQuotes [Char]
"trap \"echo $((1+num))\" EXIT DEBUG"
checkTrapQuotes :: CommandCheck
checkTrapQuotes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"trap") ([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 a. a -> m a
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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    warning :: Id -> m ()
warning Id
id = Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2064 [Char]
"Use single quotes, otherwise this expands now rather than when signalled."
    checkExpansions :: Token -> m ()
checkExpansions (T_DollarExpansion Id
id [Token]
_) = 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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

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

returnOrExit :: (Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit Id -> f ()
multi Id -> f ()
invalid = ([Token] -> f ()
f ([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 ([Char] -> Bool
isInvalid ([Char] -> Bool) -> [Char] -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> [Char]
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 a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isInvalid :: [Char] -> Bool
isInvalid [Char]
s = [Char] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
s Bool -> Bool -> Bool
|| (Char -> Bool) -> [Char] -> 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) [Char]
s Bool -> Bool -> Bool
|| [Char] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Char]
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
5
        Bool -> Bool -> Bool
|| let value :: Code
value = ([Char] -> Code
forall a. Read a => [Char] -> a
read [Char]
s :: Integer) in Code
value Code -> Code -> Bool
forall a. Ord a => a -> a -> Bool
> Code
255

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


prop_checkFindExecWithSingleArgument1 :: Bool
prop_checkFindExecWithSingleArgument1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindExecWithSingleArgument [Char]
"find . -exec 'cat {} | wc -l' \\;"
prop_checkFindExecWithSingleArgument2 :: Bool
prop_checkFindExecWithSingleArgument2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindExecWithSingleArgument [Char]
"find . -execdir 'cat {} | wc -l' +"
prop_checkFindExecWithSingleArgument3 :: Bool
prop_checkFindExecWithSingleArgument3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindExecWithSingleArgument [Char]
"find . -exec wc -l {} \\;"
checkFindExecWithSingleArgument :: CommandCheck
checkFindExecWithSingleArgument = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") ([Token] -> Analysis
f ([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)
forall (m :: * -> *) a. Monad m => [m a] -> m [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
        [Char]
execS <- Token -> Maybe [Char]
getLiteralString Token
exec
        [Char]
termS <- Token -> Maybe [Char]
getLiteralString Token
term
        let cmdS :: [Char]
cmdS = [Char] -> Token -> [Char]
getLiteralStringDef [Char]
" " Token
arg

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


prop_checkUnusedEchoEscapes1 :: Bool
prop_checkUnusedEchoEscapes1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo 'foo\\nbar\\n'"
prop_checkUnusedEchoEscapes2 :: Bool
prop_checkUnusedEchoEscapes2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo -e 'foi\\nbar'"
prop_checkUnusedEchoEscapes3 :: Bool
prop_checkUnusedEchoEscapes3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo \"n:\\t42\""
prop_checkUnusedEchoEscapes4 :: Bool
prop_checkUnusedEchoEscapes4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo lol"
prop_checkUnusedEchoEscapes5 :: Bool
prop_checkUnusedEchoEscapes5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo -n -e '\n'"
prop_checkUnusedEchoEscapes6 :: Bool
prop_checkUnusedEchoEscapes6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\506'"
prop_checkUnusedEchoEscapes7 :: Bool
prop_checkUnusedEchoEscapes7 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\5a'"
prop_checkUnusedEchoEscapes8 :: Bool
prop_checkUnusedEchoEscapes8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\8a'"
prop_checkUnusedEchoEscapes9 :: Bool
prop_checkUnusedEchoEscapes9 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\d5a'"
prop_checkUnusedEchoEscapes10 :: Bool
prop_checkUnusedEchoEscapes10 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\x4a'"
prop_checkUnusedEchoEscapes11 :: Bool
prop_checkUnusedEchoEscapes11 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\xat'"
prop_checkUnusedEchoEscapes12 :: Bool
prop_checkUnusedEchoEscapes12 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes [Char]
"echo '\\xth'"
checkUnusedEchoEscapes :: CommandCheck
checkUnusedEchoEscapes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"echo") Token -> Analysis
forall {m :: * -> *}.
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    hasEscapes :: Regex
hasEscapes = [Char] -> Regex
mkRegex [Char]
"\\\\([rntabefv\\']|[0-7]{1,3}|x([0-9]|[A-F]|[a-f]){1,2})"
    f :: Token -> m ()
f Token
cmd =
        [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 -> [Char] -> Bool
`hasFlag` [Char]
"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 :: [Char]
str = Token -> [Char]
onlyLiteralString Token
token
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char]
str [Char] -> Regex -> Bool
`matches` Regex
hasEscapes) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> [Char] -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
token) Code
2028 [Char]
"echo may not expand escape sequences. Use printf."


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

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

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


prop_checkFindActionPrecedence1 :: Bool
prop_checkFindActionPrecedence1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkFindActionPrecedence [Char]
"find . -name '*.wav' -o -name '*.au' -exec rm {} +"
prop_checkFindActionPrecedence2 :: Bool
prop_checkFindActionPrecedence2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindActionPrecedence [Char]
"find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)"
prop_checkFindActionPrecedence3 :: Bool
prop_checkFindActionPrecedence3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindActionPrecedence [Char]
"find . -name '*.wav' -o -name '*.au'"
checkFindActionPrecedence :: CommandCheck
checkFindActionPrecedence = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") ([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, [[Char]] -> Token -> Bool
forall {t :: * -> *}. Foldable t => t [Char] -> Token -> Bool
isParam [[Char]
"-o", [Char]
"-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 a. [a] -> 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 a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern = () -> m ()
forall a. a -> m a
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. HasCallStack => [a] -> Int -> a
!! ([Token -> Bool] -> Int
forall a. [a] -> 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 = [[Char]] -> Token -> Bool
forall {t :: * -> *}. Foldable t => t [Char] -> Token -> Bool
isParam [ [Char]
"-name", [Char]
"-regex", [Char]
"-iname", [Char]
"-iregex", [Char]
"-wholename", [Char]
"-iwholename" ]
    isAction :: Token -> Bool
isAction = [[Char]] -> Token -> Bool
forall {t :: * -> *}. Foldable t => t [Char] -> Token -> Bool
isParam [ [Char]
"-exec", [Char]
"-execdir", [Char]
"-delete", [Char]
"-print", [Char]
"-print0", [Char]
"-fls", [Char]
"-fprint", [Char]
"-fprint0", [Char]
"-fprintf", [Char]
"-ls", [Char]
"-ok", [Char]
"-okdir", [Char]
"-printf" ]
    isParam :: t [Char] -> Token -> Bool
isParam t [Char]
strs Token
t = 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
        [Char]
param <- Token -> Maybe [Char]
getLiteralString Token
t
        Bool -> Maybe Bool
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ [Char]
param [Char] -> t [Char] -> Bool
forall a. Eq a => a -> t a -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t [Char]
strs
    warnFor :: Token -> m ()
warnFor Token
t = Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2146 [Char]
"This action ignores everything before the -o. Use \\( \\) to group."


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


prop_checkNonportableSignals1 :: Bool
prop_checkNonportableSignals1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f 8"
prop_checkNonportableSignals2 :: Bool
prop_checkNonportableSignals2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkNonportableSignals [Char]
"trap f 0"
prop_checkNonportableSignals3 :: Bool
prop_checkNonportableSignals3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkNonportableSignals [Char]
"trap f 14"
prop_checkNonportableSignals4 :: Bool
prop_checkNonportableSignals4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f SIGKILL"
prop_checkNonportableSignals5 :: Bool
prop_checkNonportableSignals5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f 9"
prop_checkNonportableSignals6 :: Bool
prop_checkNonportableSignals6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkNonportableSignals [Char]
"trap f stop"
prop_checkNonportableSignals7 :: Bool
prop_checkNonportableSignals7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkNonportableSignals [Char]
"trap 'stop' int"
checkNonportableSignals :: CommandCheck
checkNonportableSignals = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"trap") ([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 a. a -> m a
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
        [Char]
str <- Token -> Maybe [Char]
getLiteralString Token
param
        let id :: Id
id = Token -> Id
getId Token
param
        m () -> Maybe (m ())
forall a. a -> Maybe a
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 -> [Char] -> Maybe (m ())) -> Maybe (m ()))
-> [Id -> [Char] -> Maybe (m ())] -> [m ()]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Id -> [Char] -> Maybe (m ())
f -> Id -> [Char] -> Maybe (m ())
f Id
id [Char]
str) [
            Id -> [Char] -> Maybe (m ())
forall {m :: * -> *} {m :: * -> *}.
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> [Char] -> m (m ())
checkNumeric,
            Id -> [Char] -> Maybe (m ())
forall {m :: * -> *} {m :: * -> *}.
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> [Char] -> m (m ())
checkUntrappable
            ]

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

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


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

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


-- This is hard to get right without properly parsing ssh args
prop_checkSshCmdStr1 :: Bool
prop_checkSshCmdStr1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSshCommandString [Char]
"ssh host \"echo $PS1\""
prop_checkSshCmdStr2 :: Bool
prop_checkSshCmdStr2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSshCommandString [Char]
"ssh host \"ls foo\""
prop_checkSshCmdStr3 :: Bool
prop_checkSshCmdStr3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSshCommandString [Char]
"ssh \"$host\""
prop_checkSshCmdStr4 :: Bool
prop_checkSshCmdStr4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSshCommandString [Char]
"ssh -i key \"$host\""
checkSshCommandString :: CommandCheck
checkSshCommandString = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"ssh") ([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 = [Char]
"-" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` ([[Char]] -> [Char]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall a b. (a -> b) -> a -> b
$ Token -> [[Char]]
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. HasCallStack => [a] -> a
last [Token]
r
            ([Token], [Token])
_ -> () -> m ()
forall a. a -> m a
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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
x) Code
2029
                [Char]
"Note that, unescaped, this expands on the client side."
    checkArg Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

checkPrintfVar :: CommandCheck
checkPrintfVar = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"printf") ([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 [Char]
getLiteralString Token
doubledash Maybe [Char] -> Maybe [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
"--" = [Token] -> m ()
f [Token]
rest
    f (Token
dashv:Token
var:[Token]
rest) | Token -> Maybe [Char]
getLiteralString Token
dashv Maybe [Char] -> Maybe [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
"-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 a. a -> m a
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
            [Char]
string <- Token -> Maybe [Char]
getLiteralString Token
format
            let formats :: [Char]
formats = [Char] -> [Char]
getPrintfFormats [Char]
string
            let formatCount :: Int
formatCount = [Char] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Char]
formats
            let argCount :: Int
argCount = t Token -> Int
forall a. t a -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Token
more
            let pluraliseIfMany :: [Char] -> a -> [Char]
pluraliseIfMany [Char]
word a
n = if a
n a -> a -> Bool
forall a. Ord a => a -> a -> Bool
> a
1 then [Char]
word [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"s" else [Char]
word

            m () -> Maybe (m ())
forall a. a -> Maybe a
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 a. a -> m a
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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
format) Code
2182
                        [Char]
"This printf format string has no variables. Other arguments are ignored."
                | (Token -> Bool) -> t Token -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
mayBecomeMultipleArgs t Token
more ->
                    () -> m ()
forall a. a -> m a
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
&& [Char] -> Int -> Bool
onlyTrailingTs [Char]
formats Int
argCount ->
                    () -> m ()
forall a. a -> m a
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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return () -- Great: a suitable number of arguments
                | Bool
otherwise ->
                    Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
format) Code
2183 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$
                        [Char]
"This format string has " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
formatCount [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> Int -> [Char]
forall {a}. (Ord a, Num a) => [Char] -> a -> [Char]
pluraliseIfMany [Char]
"variable" Int
formatCount [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
                        [Char]
", but is passed " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
argCount [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> Int -> [Char]
forall {a}. (Ord a, Num a) => [Char] -> a -> [Char]
pluraliseIfMany [Char]
" argument" Int
argCount [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"."

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


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

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


prop_checkUuoeCmd1 :: Bool
prop_checkUuoeCmd1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo $(date)"
prop_checkUuoeCmd2 :: Bool
prop_checkUuoeCmd2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo `date`"
prop_checkUuoeCmd3 :: Bool
prop_checkUuoeCmd3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo \"$(date)\""
prop_checkUuoeCmd4 :: Bool
prop_checkUuoeCmd4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUuoeCmd [Char]
"echo \"`date`\""
prop_checkUuoeCmd5 :: Bool
prop_checkUuoeCmd5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUuoeCmd [Char]
"echo \"The time is $(date)\""
prop_checkUuoeCmd6 :: Bool
prop_checkUuoeCmd6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUuoeCmd [Char]
"echo \"$(<file)\""
checkUuoeCmd :: CommandCheck
checkUuoeCmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"echo") ([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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style Id
id Code
2005 [Char]
"Useless echo? Instead of 'echo $(cmd)', just use 'cmd'."
    f :: [Token] -> f ()
f [Token
token] = 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 a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

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


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

prop_checkReadExpansions1 :: Bool
prop_checkReadExpansions1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read $var"
prop_checkReadExpansions2 :: Bool
prop_checkReadExpansions2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read -r $var"
prop_checkReadExpansions3 :: Bool
prop_checkReadExpansions3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read -p $var"
prop_checkReadExpansions4 :: Bool
prop_checkReadExpansions4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read -rd $delim name"
prop_checkReadExpansions5 :: Bool
prop_checkReadExpansions5 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read \"$var\""
prop_checkReadExpansions6 :: Bool
prop_checkReadExpansions6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read -a $var"
prop_checkReadExpansions7 :: Bool
prop_checkReadExpansions7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read $1"
prop_checkReadExpansions8 :: Bool
prop_checkReadExpansions8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkReadExpansions [Char]
"read ${var?}"
prop_checkReadExpansions9 :: Bool
prop_checkReadExpansions9 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkReadExpansions [Char]
"read arr[val]"
checkReadExpansions :: CommandCheck
checkReadExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"read") Token -> Analysis
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check
  where
    options :: [Token] -> Maybe [([Char], (Token, Token))]
options = [Char] -> [Token] -> Maybe [([Char], (Token, Token))]
getGnuOpts [Char]
flagsForRead
    getVars :: Token -> [Token]
getVars Token
cmd = [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
        [([Char], (Token, Token))]
opts <- [Token] -> Maybe [([Char], (Token, Token))]
options ([Token] -> Maybe [([Char], (Token, Token))])
-> [Token] -> Maybe [([Char], (Token, Token))]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd
        [Token] -> Maybe [Token]
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return [Token
y | ([Char]
x,(Token
_, Token
y)) <- [([Char], (Token, Token))]
opts, [Char] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
x Bool -> Bool -> Bool
|| [Char]
x [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"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
        [Char]
name <- Token -> Maybe [Char]
getSingleUnmodifiedBracedString Token
t
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ [Char] -> Bool
isVariableName [Char]
name   -- e.g. not $1
        m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> ([Char] -> m ()) -> [Char] -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2229 ([Char] -> Maybe (m ())) -> [Char] -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            [Char]
"This does not read '" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
name [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"'. 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 -> [Char] -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
word) Code
2313 ([Char] -> f ()) -> [Char] -> f ()
forall a b. (a -> b) -> a -> b
$
                [Char]
"Quote array indices to avoid them expanding as globs."

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

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

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


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


prop_checkUnsetGlobs1 :: Bool
prop_checkUnsetGlobs1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnsetGlobs [Char]
"unset foo[1]"
prop_checkUnsetGlobs2 :: Bool
prop_checkUnsetGlobs2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnsetGlobs [Char]
"unset foo"
prop_checkUnsetGlobs3 :: Bool
prop_checkUnsetGlobs3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnsetGlobs [Char]
"unset foo[$i]"
prop_checkUnsetGlobs4 :: Bool
prop_checkUnsetGlobs4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkUnsetGlobs [Char]
"unset foo[x${i}y]"
prop_checkUnsetGlobs5 :: Bool
prop_checkUnsetGlobs5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnsetGlobs [Char]
"unset foo]["
checkUnsetGlobs :: CommandCheck
checkUnsetGlobs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"unset") ((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 -> [Char] -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
arg) Code
2184 [Char]
"Quote arguments to unset so they're not glob expanded."


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

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


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

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

prop_checkTimedCommand1 :: Bool
prop_checkTimedCommand1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTimedCommand [Char]
"#!/bin/sh\ntime -p foo | bar"
prop_checkTimedCommand2 :: Bool
prop_checkTimedCommand2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkTimedCommand [Char]
"#!/bin/dash\ntime ( foo; bar; )"
prop_checkTimedCommand3 :: Bool
prop_checkTimedCommand3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkTimedCommand [Char]
"#!/bin/sh\ntime sleep 1"
checkTimedCommand :: CommandCheck
checkTimedCommand = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"time") 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, Shell
BusyboxSh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
            let cmd :: Token
cmd = [Token] -> Token
forall a. HasCallStack => [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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
c) Code
2176 [Char]
"'time' is undefined for pipelines. time single stage or bash -c instead."
            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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
cmd) Code
2177 [Char]
"'time' is undefined for compound commands, time sh -c instead."
    f Token
_ = () -> m ()
forall a. a -> m a
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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Token
a
            Token
_ -> [Char] -> m Token
forall a. [Char] -> m a
forall (m :: * -> *) a. MonadFail m => [Char] -> m a
fail [Char]
""
    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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            Token
_ -> Bool -> m Bool
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False

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

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

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

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

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

prop_checkWhileGetoptsCase1 :: Bool
prop_checkWhileGetoptsCase1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case $x in a) foo;; esac; done"
prop_checkWhileGetoptsCase2 :: Bool
prop_checkWhileGetoptsCase2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done"
prop_checkWhileGetoptsCase3 :: Bool
prop_checkWhileGetoptsCase3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done"
prop_checkWhileGetoptsCase4 :: Bool
prop_checkWhileGetoptsCase4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done"
prop_checkWhileGetoptsCase5 :: Bool
prop_checkWhileGetoptsCase5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done"
prop_checkWhileGetoptsCase6 :: Bool
prop_checkWhileGetoptsCase6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case $y in a) foo;; esac; done"
prop_checkWhileGetoptsCase7 :: Bool
prop_checkWhileGetoptsCase7 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do case x$x in xa) foo;; xb) foo;; esac; done"
prop_checkWhileGetoptsCase8 :: Bool
prop_checkWhileGetoptsCase8 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase [Char]
"while getopts 'a:b' x; do x=a; case $x in a) foo;; esac; done"
checkWhileGetoptsCase :: CommandCheck
checkWhileGetoptsCase = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"getopts") Token -> Analysis
f
  where
    f :: Token -> Analysis
    f :: Token -> Analysis
f t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:Token
arg1:Token
name:[Token]
_))  = do
        NonEmpty Token
path <- Token
-> RWST Parameters [TokenComment] Cache Identity (NonEmpty Token)
forall {m :: * -> *}.
MonadReader Parameters m =>
Token -> m (NonEmpty 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
            [Char]
options <- Token -> Maybe [Char]
getLiteralString Token
arg1
            [Char]
getoptsVar <- Token -> Maybe [Char]
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 (NonEmpty Token -> [Token]
forall a. NonEmpty a -> [a]
NE.toList NonEmpty Token
path)
            T_CaseExpression Id
id Token
var [(CaseType, [Token], [Token])]
list <- (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 a. a -> Maybe a
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
_ [Char]
caseVar] <- [Token] -> Maybe [Token]
forall a. a -> Maybe a
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
$ [Char]
caseVar [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
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 -> [Char] -> Bool
modifiesVariable Parameters
params (Id -> [Token] -> Token
T_BraceGroup (Int -> Id
Id Int
0) [Token]
body) [Char]
getoptsVar

            Analysis -> Maybe Analysis
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Analysis -> Maybe Analysis) -> Analysis -> Maybe Analysis
forall a b. (a -> b) -> a -> b
$ Id -> [[Char]] -> Id -> [(CaseType, [Token], [Token])] -> Analysis
check (Token -> Id
getId Token
arg1) ((Char -> [Char]) -> [Char] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map (Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
:[]) ([Char] -> [[Char]]) -> [Char] -> [[Char]]
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> [Char] -> [Char]
forall a. (a -> Bool) -> [a] -> [a]
filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
':') [Char]
options) Id
id [(CaseType, [Token], [Token])]
list
    f Token
_ = () -> Analysis
forall a. a -> RWST Parameters [TokenComment] Cache Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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

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

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

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

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

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

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

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

    whileLoop :: Token -> Maybe Bool
whileLoop Token
t =
        case Token
t of
            T_WhileExpression {} -> Bool -> Maybe Bool
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Script {} -> Bool -> Maybe Bool
forall a. a -> Maybe a
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 a. a -> Maybe a
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 -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r $1/$2"
prop_checkCatastrophicRm2 :: Bool
prop_checkCatastrophicRm2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r /home/$foo"
prop_checkCatastrophicRm3 :: Bool
prop_checkCatastrophicRm3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r /home/${USER:?}/*"
prop_checkCatastrophicRm4 :: Bool
prop_checkCatastrophicRm4 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -fr /home/$(whoami)/*"
prop_checkCatastrophicRm5 :: Bool
prop_checkCatastrophicRm5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r /home/${USER:-thing}/*"
prop_checkCatastrophicRm6 :: Bool
prop_checkCatastrophicRm6 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm --recursive /etc/*$config*"
prop_checkCatastrophicRm8 :: Bool
prop_checkCatastrophicRm8 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -rf /home"
prop_checkCatastrophicRm10 :: Bool
prop_checkCatastrophicRm10 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}"
prop_checkCatastrophicRm11 :: Bool
prop_checkCatastrophicRm11 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r /{bin,sbin}/$exec"
prop_checkCatastrophicRm12 :: Bool
prop_checkCatastrophicRm12 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -r /{{usr,},{bin,sbin}}/$exec"
prop_checkCatastrophicRm13 :: Bool
prop_checkCatastrophicRm13 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkCatastrophicRm [Char]
"rm -r /{{a,b},{c,d}}/$exec"
prop_checkCatastrophicRmA :: Bool
prop_checkCatastrophicRmA = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -rf /usr /lib/nvidia-current/xorg/xorg"
prop_checkCatastrophicRmB :: Bool
prop_checkCatastrophicRmB = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkCatastrophicRm [Char]
"rm -rf \"$STEAMROOT/\"*"
checkCatastrophicRm :: CommandCheck
checkCatastrophicRm = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"rm") ((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, [Char]) -> Bool) -> [(Token, [Char])] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (([Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"r", [Char]
"R", [Char]
"recursive"]) ([Char] -> Bool)
-> ((Token, [Char]) -> [Char]) -> (Token, [Char]) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, [Char]) -> [Char]
forall a b. (a, b) -> b
snd) ([(Token, [Char])] -> Bool)
-> (Token -> [(Token, [Char])]) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, [Char])]
getAllFlags

    checkWord :: Token -> f ()
checkWord Token
token =
        case Token -> Maybe [Char]
getLiteralString Token
token of
            Just [Char]
str ->
                Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Char] -> [Char]
forall {t :: * -> *}. Foldable t => t Char -> [Char]
fixPath [Char]
str [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
importantPaths) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Code -> [Char] -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
token) Code
2114 [Char]
"Warning: deletes a system directory."
            Maybe [Char]
Nothing ->
                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
        [Char]
filename <- Token -> Maybe [Char]
getPotentialPath Token
token
        let path :: [Char]
path = [Char] -> [Char]
forall {t :: * -> *}. Foldable t => t Char -> [Char]
fixPath [Char]
filename
        m () -> Maybe (m ())
forall a. a -> Maybe a
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 ([Char]
path [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]]
importantPaths) (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
token) Code
2115 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$ [Char]
"Use \"${var:?}\" to ensure this never expands to " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
path [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" ."

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

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

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


prop_checkLetUsage1 :: Bool
prop_checkLetUsage1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkLetUsage [Char]
"let a=1"
prop_checkLetUsage2 :: Bool
prop_checkLetUsage2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkLetUsage [Char]
"(( a=1 ))"
checkLetUsage :: CommandCheck
checkLetUsage = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"let") 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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style (Token -> Id
getId Token
t) Code
2219 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$ [Char]
"Instead of 'let expr', prefer (( expr )) ."


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

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

checkCpArguments :: CommandCheck
checkCpArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"cp") ((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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
t) Code
2225 [Char]
"This cp has no destination. Check the arguments."

checkLnArguments :: CommandCheck
checkLnArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"ln") ((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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2226 [Char]
"This ln has no destination. Check the arguments, or specify '.' explicitly."


prop_checkFindRedirections1 :: Bool
prop_checkFindRedirections1 = CommandCheck -> [Char] -> Bool
verify    CommandCheck
checkFindRedirections [Char]
"find . -exec echo {} > file \\;"
prop_checkFindRedirections2 :: Bool
prop_checkFindRedirections2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindRedirections [Char]
"find . -exec echo {} \\; > file"
prop_checkFindRedirections3 :: Bool
prop_checkFindRedirections3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkFindRedirections [Char]
"find . -execdir sh -c 'foo > file' \\;"
checkFindRedirections :: CommandCheck
checkFindRedirections = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"find") 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 a. Ord a => [a] -> a
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 a. Ord a => [a] -> a
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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
minRedir Code
2227
                        [Char]
"Redirection applies to the find command itself. Rewrite to work per action (or move to end)."
            Maybe Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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

prop_checkSourceArgs1 :: Bool
prop_checkSourceArgs1 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkSourceArgs [Char]
"#!/bin/sh\n. script arg"
prop_checkSourceArgs2 :: Bool
prop_checkSourceArgs2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSourceArgs [Char]
"#!/bin/sh\n. script"
prop_checkSourceArgs3 :: Bool
prop_checkSourceArgs3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkSourceArgs [Char]
"#!/bin/bash\n. script arg"
checkSourceArgs :: CommandCheck
checkSourceArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
".") 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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
arg1) Code
2240 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$
                [Char]
"The dot command does not support arguments in sh/dash. Set them as variables."
            [Token]
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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


prop_checkArgComparison1 :: Bool
prop_checkArgComparison1 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare a = b"
prop_checkArgComparison2 :: Bool
prop_checkArgComparison2 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare a =b"
prop_checkArgComparison3 :: Bool
prop_checkArgComparison3 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare a=b"
prop_checkArgComparison4 :: Bool
prop_checkArgComparison4 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"export") [Char]
"export a +=b"
prop_checkArgComparison7 :: Bool
prop_checkArgComparison7 = CommandCheck -> [Char] -> Bool
verifyNot ([Char] -> CommandCheck
checkArgComparison [Char]
"declare") [Char]
"declare -a +i foo"
prop_checkArgComparison8 :: Bool
prop_checkArgComparison8 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"let") [Char]
"let x = 0"
prop_checkArgComparison9 :: Bool
prop_checkArgComparison9 = CommandCheck -> [Char] -> Bool
verify ([Char] -> CommandCheck
checkArgComparison [Char]
"alias") [Char]
"alias x =0"
-- This mirrors checkSecondArgIsComparison but for arguments to local/readonly/declare/export
checkArgComparison :: [Char] -> CommandCheck
checkArgComparison [Char]
cmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
cmd) 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
        [Char]
str <- Token -> Maybe [Char]
getLeadingUnquotedString Token
arg
        case [Char]
str of
            Char
'=':[Char]
_ ->
                m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
headId Token
arg) Code
2290 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$
                    [Char]
"Remove spaces around = to assign."
            Char
'+':Char
'=':[Char]
_ ->
                m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
headId Token
arg) Code
2290 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$
                    [Char]
"Remove spaces around += to append."
            [Char]
_ -> 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 ([Char]
cmd [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"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
        [Char]
str <- Token -> Maybe [Char]
getLiteralString Token
token
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ [Char]
"=" [Char] -> [Char] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` [Char]
str
        m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
err (Token -> Id
getId Token
token) Code
2290 ([Char] -> m ()) -> [Char] -> m ()
forall a b. (a -> b) -> a -> b
$
            [Char]
"Remove spaces around = to assign."

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


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

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

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

            -- Don't warn about local variables that are declared readonly,
            -- because the workaround `local x; x=$(false); local -r x;` is annoying
            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 a. a -> Maybe a
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
_ [Char]
_ [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 -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn Id
id Code
2155 [Char]
"Declare and assign separately to avoid masking return values."
    checkArgs Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isLocalInFunction :: [Char] -> Bool
isLocalInFunction = ([Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"local", [Char]
"declare", [Char]
"typeset"])
    isScopedFunction :: Shell -> Token -> Bool
isScopedFunction Shell
shell Token
t =
        case Token
t of
            T_BatsTest {} -> Bool
True
            -- In ksh, only functions declared with 'function' have their own scope
            T_Function Id
_ (FunctionKeyword Bool
hasFunction) FunctionParentheses
_ [Char]
_ Token
_ -> Shell
shell 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 -> [Char] -> Bool
verify CommandCheck
checkUnquotedEchoSpaces [Char]
"echo foo         bar"
prop_checkUnquotedEchoSpaces2 :: Bool
prop_checkUnquotedEchoSpaces2 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo       foo"
prop_checkUnquotedEchoSpaces3 :: Bool
prop_checkUnquotedEchoSpaces3 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo foo  bar"
prop_checkUnquotedEchoSpaces4 :: Bool
prop_checkUnquotedEchoSpaces4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo 'foo          bar'"
prop_checkUnquotedEchoSpaces5 :: Bool
prop_checkUnquotedEchoSpaces5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"echo a > myfile.txt b"
prop_checkUnquotedEchoSpaces6 :: Bool
prop_checkUnquotedEchoSpaces6 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkUnquotedEchoSpaces [Char]
"        echo foo\\\n        bar"
checkUnquotedEchoSpaces :: CommandCheck
checkUnquotedEchoSpaces = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Basename [Char]
"echo") 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
M.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
M.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 a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> [Char] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
info (Token -> Id
getId Token
t) Code
2291 [Char]
"Quote repeated spaces to avoid them collapsing into one."

    hasSpacesBetween :: t Position -> ((Position, Position), (Position, Position)) -> Bool
hasSpacesBetween t Position
redirs ((Position
a,Position
b), (Position
c,Position
d)) =
        Position -> Code
posLine Position
a 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 -> [Char] -> Bool
verify CommandCheck
checkEvalArray  [Char]
"eval $@"
prop_checkEvalArray2 :: Bool
prop_checkEvalArray2 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkEvalArray  [Char]
"eval \"${args[@]}\""
prop_checkEvalArray3 :: Bool
prop_checkEvalArray3 = CommandCheck -> [Char] -> Bool
verify CommandCheck
checkEvalArray  [Char]
"eval \"${args[@]@Q}\""
prop_checkEvalArray4 :: Bool
prop_checkEvalArray4 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkEvalArray  [Char]
"eval \"${args[*]@Q}\""
prop_checkEvalArray5 :: Bool
prop_checkEvalArray5 = CommandCheck -> [Char] -> Bool
verifyNot CommandCheck
checkEvalArray  [Char]
"eval \"$*\""
checkEvalArray :: CommandCheck
checkEvalArray = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck ([Char] -> CommandName
Exactly [Char]
"eval") ((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 -> [Char] -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
style (Token -> Id
getId Token
t) Code
2293 [Char]
"When eval'ing @Q-quoted words, use * rather than @ as the index."
            else Id -> Code -> [Char] -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> [Char] -> m ()
warn (Token -> Id
getId Token
t) Code
2294 [Char]
"eval negates the benefit of arrays. Drop eval to preserve whitespace/symbols (or eval as string)."

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


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

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

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

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

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

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


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