{-
    Copyright 2012-2016 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 #-}
module ShellCheck.Checks.ShellSupport (checker , ShellCheck.Checks.ShellSupport.runTests) where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib
import ShellCheck.Interface
import ShellCheck.Regex

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

data ForShell = ForShell [Shell] (Token -> Analysis)

getChecker :: Parameters -> [ForShell] -> Checker
getChecker params :: Parameters
params list :: [ForShell]
list = Checker :: (Root -> Analysis) -> (Token -> Analysis) -> Checker
Checker {
        perScript :: Root -> Analysis
perScript = Root -> Analysis
forall b. b -> Analysis
nullCheck,
        perToken :: Token -> Analysis
perToken = ((Token -> Analysis) -> (Token -> Analysis) -> Token -> Analysis)
-> (Token -> Analysis) -> [Token -> Analysis] -> Token -> Analysis
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (Token -> Analysis) -> (Token -> Analysis) -> Token -> Analysis
forall a. (a -> Analysis) -> (a -> Analysis) -> a -> Analysis
composeAnalyzers Token -> Analysis
forall b. b -> Analysis
nullCheck ([Token -> Analysis] -> Token -> Analysis)
-> [Token -> Analysis] -> Token -> Analysis
forall a b. (a -> b) -> a -> b
$ (ForShell -> Maybe (Token -> Analysis))
-> [ForShell] -> [Token -> Analysis]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ForShell -> Maybe (Token -> Analysis)
forall (m :: * -> *).
(Monad m, Alternative m) =>
ForShell -> m (Token -> Analysis)
include [ForShell]
list
    }
  where
    shell :: Shell
shell = Parameters -> Shell
shellType Parameters
params
    include :: ForShell -> m (Token -> Analysis)
include (ForShell list :: [Shell]
list a :: Token -> Analysis
a) = do
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ Shell
shell Shell -> [Shell] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Shell]
list
        (Token -> Analysis) -> m (Token -> Analysis)
forall (m :: * -> *) a. Monad m => a -> m a
return Token -> Analysis
a

checker :: Parameters -> Checker
checker params :: Parameters
params = Parameters -> [ForShell] -> Checker
getChecker Parameters
params [ForShell]
checks

checks :: [ForShell]
checks = [
    ForShell
checkForDecimals
    ,ForShell
checkBashisms
    ,ForShell
checkEchoSed
    ,ForShell
checkBraceExpansionVars
    ,ForShell
checkMultiDimensionalArrays
    ,ForShell
checkPS1Assignments
    ]

testChecker :: ForShell -> Checker
testChecker (ForShell _ t :: Token -> Analysis
t) =
    Checker :: (Root -> Analysis) -> (Token -> Analysis) -> Checker
Checker {
        perScript :: Root -> Analysis
perScript = Root -> Analysis
forall b. b -> Analysis
nullCheck,
        perToken :: Token -> Analysis
perToken = Token -> Analysis
t
    }
verify :: ForShell -> String -> Bool
verify c :: ForShell
c s :: String
s = Checker -> String -> Maybe Bool
producesComments (ForShell -> Checker
testChecker ForShell
c) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
verifyNot :: ForShell -> String -> Bool
verifyNot c :: ForShell
c s :: String
s = Checker -> String -> Maybe Bool
producesComments (ForShell -> Checker
testChecker ForShell
c) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

prop_checkForDecimals1 :: Bool
prop_checkForDecimals1 = ForShell -> String -> Bool
verify ForShell
checkForDecimals "((3.14*c))"
prop_checkForDecimals2 :: Bool
prop_checkForDecimals2 = ForShell -> String -> Bool
verify ForShell
checkForDecimals "foo[1.2]=bar"
prop_checkForDecimals3 :: Bool
prop_checkForDecimals3 = ForShell -> String -> Bool
verifyNot ForShell
checkForDecimals "declare -A foo; foo[1.2]=bar"
checkForDecimals :: ForShell
checkForDecimals = [Shell] -> (Token -> Analysis) -> ForShell
ForShell [Shell
Sh, Shell
Dash, Shell
Bash] Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t@(TA_Expansion id :: Id
id _) = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
str <- Token -> Maybe String
getLiteralString Token
t
        Char
first <- String
str String -> Int -> Maybe Char
forall a. [a] -> Int -> Maybe a
!!! 0
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Char -> Bool
isDigit Char
first Bool -> Bool -> Bool
&& '.' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id 2079 "(( )) doesn't support decimals. Use bc or awk."
    f _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkBashisms :: Bool
prop_checkBashisms = ForShell -> String -> Bool
verify ForShell
checkBashisms "while read a; do :; done < <(a)"
prop_checkBashisms2 :: Bool
prop_checkBashisms2 = ForShell -> String -> Bool
verify ForShell
checkBashisms "[ foo -nt bar ]"
prop_checkBashisms3 :: Bool
prop_checkBashisms3 = ForShell -> String -> Bool
verify ForShell
checkBashisms "echo $((i++))"
prop_checkBashisms4 :: Bool
prop_checkBashisms4 = ForShell -> String -> Bool
verify ForShell
checkBashisms "rm !(*.hs)"
prop_checkBashisms5 :: Bool
prop_checkBashisms5 = ForShell -> String -> Bool
verify ForShell
checkBashisms "source file"
prop_checkBashisms6 :: Bool
prop_checkBashisms6 = ForShell -> String -> Bool
verify ForShell
checkBashisms "[ \"$a\" == 42 ]"
prop_checkBashisms7 :: Bool
prop_checkBashisms7 = ForShell -> String -> Bool
verify ForShell
checkBashisms "echo ${var[1]}"
prop_checkBashisms8 :: Bool
prop_checkBashisms8 = ForShell -> String -> Bool
verify ForShell
checkBashisms "echo ${!var[@]}"
prop_checkBashisms9 :: Bool
prop_checkBashisms9 = ForShell -> String -> Bool
verify ForShell
checkBashisms "echo ${!var*}"
prop_checkBashisms10 :: Bool
prop_checkBashisms10= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo ${var:4:12}"
prop_checkBashisms11 :: Bool
prop_checkBashisms11= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "echo ${var:-4}"
prop_checkBashisms12 :: Bool
prop_checkBashisms12= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo ${var//foo/bar}"
prop_checkBashisms13 :: Bool
prop_checkBashisms13= ForShell -> String -> Bool
verify ForShell
checkBashisms "exec -c env"
prop_checkBashisms14 :: Bool
prop_checkBashisms14= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo -n \"Foo: \""
prop_checkBashisms15 :: Bool
prop_checkBashisms15= ForShell -> String -> Bool
verify ForShell
checkBashisms "let n++"
prop_checkBashisms16 :: Bool
prop_checkBashisms16= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo $RANDOM"
prop_checkBashisms17 :: Bool
prop_checkBashisms17= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo $((RANDOM%6+1))"
prop_checkBashisms18 :: Bool
prop_checkBashisms18= ForShell -> String -> Bool
verify ForShell
checkBashisms "foo &> /dev/null"
prop_checkBashisms19 :: Bool
prop_checkBashisms19= ForShell -> String -> Bool
verify ForShell
checkBashisms "foo > file*.txt"
prop_checkBashisms20 :: Bool
prop_checkBashisms20= ForShell -> String -> Bool
verify ForShell
checkBashisms "read -ra foo"
prop_checkBashisms21 :: Bool
prop_checkBashisms21= ForShell -> String -> Bool
verify ForShell
checkBashisms "[ -a foo ]"
prop_checkBashisms22 :: Bool
prop_checkBashisms22= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "[ foo -a bar ]"
prop_checkBashisms23 :: Bool
prop_checkBashisms23= ForShell -> String -> Bool
verify ForShell
checkBashisms "trap mything ERR INT"
prop_checkBashisms24 :: Bool
prop_checkBashisms24= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "trap mything INT TERM"
prop_checkBashisms25 :: Bool
prop_checkBashisms25= ForShell -> String -> Bool
verify ForShell
checkBashisms "cat < /dev/tcp/host/123"
prop_checkBashisms26 :: Bool
prop_checkBashisms26= ForShell -> String -> Bool
verify ForShell
checkBashisms "trap mything ERR SIGTERM"
prop_checkBashisms27 :: Bool
prop_checkBashisms27= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo *[^0-9]*"
prop_checkBashisms28 :: Bool
prop_checkBashisms28= ForShell -> String -> Bool
verify ForShell
checkBashisms "exec {n}>&2"
prop_checkBashisms29 :: Bool
prop_checkBashisms29= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo ${!var}"
prop_checkBashisms30 :: Bool
prop_checkBashisms30= ForShell -> String -> Bool
verify ForShell
checkBashisms "printf -v '%s' \"$1\""
prop_checkBashisms31 :: Bool
prop_checkBashisms31= ForShell -> String -> Bool
verify ForShell
checkBashisms "printf '%q' \"$1\""
prop_checkBashisms32 :: Bool
prop_checkBashisms32= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\n[ foo -nt bar ]"
prop_checkBashisms33 :: Bool
prop_checkBashisms33= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho -n foo"
prop_checkBashisms34 :: Bool
prop_checkBashisms34= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\necho -n foo"
prop_checkBashisms35 :: Bool
prop_checkBashisms35= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\nlocal foo"
prop_checkBashisms36 :: Bool
prop_checkBashisms36= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\nread -p foo -r bar"
prop_checkBashisms37 :: Bool
prop_checkBashisms37= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "HOSTNAME=foo; echo $HOSTNAME"
prop_checkBashisms38 :: Bool
prop_checkBashisms38= ForShell -> String -> Bool
verify ForShell
checkBashisms "RANDOM=9; echo $RANDOM"
prop_checkBashisms39 :: Bool
prop_checkBashisms39= ForShell -> String -> Bool
verify ForShell
checkBashisms "foo-bar() { true; }"
prop_checkBashisms40 :: Bool
prop_checkBashisms40= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo $(<file)"
prop_checkBashisms41 :: Bool
prop_checkBashisms41= ForShell -> String -> Bool
verify ForShell
checkBashisms "echo `<file`"
prop_checkBashisms42 :: Bool
prop_checkBashisms42= ForShell -> String -> Bool
verify ForShell
checkBashisms "trap foo int"
prop_checkBashisms43 :: Bool
prop_checkBashisms43= ForShell -> String -> Bool
verify ForShell
checkBashisms "trap foo sigint"
prop_checkBashisms44 :: Bool
prop_checkBashisms44= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\ntrap foo int"
prop_checkBashisms45 :: Bool
prop_checkBashisms45= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\ntrap foo INT"
prop_checkBashisms46 :: Bool
prop_checkBashisms46= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/dash\ntrap foo SIGINT"
prop_checkBashisms47 :: Bool
prop_checkBashisms47= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/dash\necho foo 42>/dev/null"
prop_checkBashisms48 :: Bool
prop_checkBashisms48= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\necho $LINENO"
prop_checkBashisms49 :: Bool
prop_checkBashisms49= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/dash\necho $MACHTYPE"
prop_checkBashisms50 :: Bool
prop_checkBashisms50= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\ncmd >& file"
prop_checkBashisms51 :: Bool
prop_checkBashisms51= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\ncmd 2>&1"
prop_checkBashisms52 :: Bool
prop_checkBashisms52= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\ncmd >&2"
prop_checkBashisms53 :: Bool
prop_checkBashisms53= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nprintf -- -f\n"
prop_checkBashisms54 :: Bool
prop_checkBashisms54= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nfoo+=bar"
prop_checkBashisms55 :: Bool
prop_checkBashisms55= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho ${@%foo}"
prop_checkBashisms56 :: Bool
prop_checkBashisms56= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\necho ${##}"
prop_checkBashisms57 :: Bool
prop_checkBashisms57= ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\nulimit -c 0"
prop_checkBashisms58 :: Bool
prop_checkBashisms58= ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nulimit -c 0"
prop_checkBashisms59 :: Bool
prop_checkBashisms59 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\njobs -s"
prop_checkBashisms60 :: Bool
prop_checkBashisms60 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\njobs -p"
prop_checkBashisms61 :: Bool
prop_checkBashisms61 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\njobs -lp"
prop_checkBashisms62 :: Bool
prop_checkBashisms62 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nexport -f foo"
prop_checkBashisms63 :: Bool
prop_checkBashisms63 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nexport -p"
prop_checkBashisms64 :: Bool
prop_checkBashisms64 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nreadonly -a"
prop_checkBashisms65 :: Bool
prop_checkBashisms65 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nreadonly -p"
prop_checkBashisms66 :: Bool
prop_checkBashisms66 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\ncd -P ."
prop_checkBashisms67 :: Bool
prop_checkBashisms67 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\ncd -P -e ."
prop_checkBashisms68 :: Bool
prop_checkBashisms68 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\numask -p"
prop_checkBashisms69 :: Bool
prop_checkBashisms69 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\numask -S"
prop_checkBashisms70 :: Bool
prop_checkBashisms70 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\ntrap -l"
prop_checkBashisms71 :: Bool
prop_checkBashisms71 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\ntype -a ls"
prop_checkBashisms72 :: Bool
prop_checkBashisms72 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\ntype ls"
prop_checkBashisms73 :: Bool
prop_checkBashisms73 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nunset -n namevar"
prop_checkBashisms74 :: Bool
prop_checkBashisms74 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nunset -f namevar"
prop_checkBashisms75 :: Bool
prop_checkBashisms75 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\necho \"-n foo\""
prop_checkBashisms76 :: Bool
prop_checkBashisms76 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\necho \"-ne foo\""
prop_checkBashisms77 :: Bool
prop_checkBashisms77 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\necho -Q foo"
prop_checkBashisms78 :: Bool
prop_checkBashisms78 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho -ne foo"
prop_checkBashisms79 :: Bool
prop_checkBashisms79 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nhash -l"
prop_checkBashisms80 :: Bool
prop_checkBashisms80 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nhash -r"
prop_checkBashisms81 :: Bool
prop_checkBashisms81 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\nhash -v"
prop_checkBashisms82 :: Bool
prop_checkBashisms82 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nset -v +o allexport -o errexit -C"
prop_checkBashisms83 :: Bool
prop_checkBashisms83 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nset --"
prop_checkBashisms84 :: Bool
prop_checkBashisms84 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nset -o pipefail"
prop_checkBashisms85 :: Bool
prop_checkBashisms85 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nset -B"
prop_checkBashisms86 :: Bool
prop_checkBashisms86 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\nset -o emacs"
prop_checkBashisms87 :: Bool
prop_checkBashisms87 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nset -o emacs"
prop_checkBashisms88 :: Bool
prop_checkBashisms88 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nset -- wget -o foo 'https://some.url'"
prop_checkBashisms89 :: Bool
prop_checkBashisms89 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nopts=$-\nset -\"$opts\""
prop_checkBashisms90 :: Bool
prop_checkBashisms90 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/sh\nset -o \"$opt\""
prop_checkBashisms91 :: Bool
prop_checkBashisms91 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\nwait -n"
prop_checkBashisms92 :: Bool
prop_checkBashisms92 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho $((16#FF))"
prop_checkBashisms93 :: Bool
prop_checkBashisms93 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho $(( 10#$(date +%m) ))"
prop_checkBashisms94 :: Bool
prop_checkBashisms94 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\n[ -v var ]"
prop_checkBashisms95 :: Bool
prop_checkBashisms95 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho $_"
prop_checkBashisms96 :: Bool
prop_checkBashisms96 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\necho $_"
prop_checkBashisms97 :: Bool
prop_checkBashisms97 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho ${var,}"
prop_checkBashisms98 :: Bool
prop_checkBashisms98 = ForShell -> String -> Bool
verify ForShell
checkBashisms "#!/bin/sh\necho ${var^^}"
prop_checkBashisms99 :: Bool
prop_checkBashisms99 = ForShell -> String -> Bool
verifyNot ForShell
checkBashisms "#!/bin/dash\necho [^f]oo"
checkBashisms :: ForShell
checkBashisms = [Shell] -> (Token -> Analysis) -> ForShell
ForShell [Shell
Sh, Shell
Dash] ((Token -> Analysis) -> ForShell)
-> (Token -> Analysis) -> ForShell
forall a b. (a -> b) -> a -> b
$ \t :: Token
t -> do
    Parameters
params <- RWST Parameters [TokenComment] Cache Identity Parameters
forall r (m :: * -> *). MonadReader r m => m r
ask
    Parameters -> Token -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
kludge Parameters
params Token
t
 where
  -- This code was copy-pasted from Analytics where params was a variable
  kludge :: Parameters -> Token -> m ()
kludge params :: Parameters
params = Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
bashism
   where
    isDash :: Bool
isDash = Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash
    warnMsg :: Id -> Code -> String -> m ()
warnMsg id :: Id
id code :: Code
code s :: String
s =
        if Bool
isDash
        then Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err  Id
id Code
code (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "In dash, " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ " not supported."
        else Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
code (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "In POSIX sh, " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ " undefined."

    bashism :: Token -> m ()
bashism (T_ProcSub id :: Id
id _ _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3001 "process substitution is"
    bashism (T_Extglob id :: Id
id _ _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3002 "extglob is"
    bashism (T_DollarSingleQuoted id :: Id
id _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3003 "$'..' is"
    bashism (T_DollarDoubleQuoted id :: Id
id _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3004 "$\"..\" is"
    bashism (T_ForArithmetic id :: Id
id _ _ _ _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3005 "arithmetic for loops are"
    bashism (T_Arithmetic id :: Id
id _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3006 "standalone ((..)) is"
    bashism (T_DollarBracket id :: Id
id _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3007 "$[..] in place of $((..)) is"
    bashism (T_SelectIn id :: Id
id _ _ _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3008 "select loops are"
    bashism (T_BraceExpansion id :: Id
id _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3009 "brace expansion is"
    bashism (T_Condition id :: Id
id DoubleBracket _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3010 "[[ ]] is"
    bashism (T_HereString id :: Id
id _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3011 "here-strings are"
    bashism (TC_Binary id :: Id
id SingleBracket op :: String
op _ _)
        | String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ "<", ">", "\\<", "\\>", "<=", ">=", "\\<=", "\\>="] =
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isDash (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3012 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "lexicographical " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"
    bashism (TC_Binary id :: Id
id SingleBracket op :: String
op _ _)
        | String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ "-ot", "-nt", "-ef" ] =
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isDash (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3013 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"
    bashism (TC_Binary id :: Id
id SingleBracket "==" _ _) =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3014 "== in place of = is"
    bashism (TC_Binary id :: Id
id SingleBracket "=~" _ _) =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3015 "=~ regex matching is"
    bashism (TC_Unary id :: Id
id SingleBracket "-v" _) =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3016 "unary -v (in place of [ -n \"${var+x}\" ]) is"
    bashism (TC_Unary id :: Id
id _ "-a" _) =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3017 "unary -a in place of -e is"
    bashism (TA_Unary id :: Id
id op :: String
op _)
        | String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ "|++", "|--", "++|", "--|"] =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3018 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '|') String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"
    bashism (TA_Binary id :: Id
id "**" _ _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3019 "exponentials are"
    bashism (T_FdRedirect id :: Id
id "&" (T_IoFile _ (T_Greater _) _)) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3020 "&> is"
    bashism (T_FdRedirect id :: Id
id "" (T_IoFile _ (T_GREATAND _) _)) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3021 ">& is"
    bashism (T_FdRedirect id :: Id
id ('{':_) _) = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3022 "named file descriptors are"
    bashism (T_FdRedirect id :: Id
id num :: String
num _)
        | (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
num Bool -> Bool -> Bool
&& String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
num Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 1 = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3023 "FDs outside 0-9 are"
    bashism (T_Assignment id :: Id
id Append _ _ _) =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3024 "+= is"
    bashism (T_IoFile id :: Id
id _ word :: Token
word) | Bool
isNetworked =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3025 "/dev/{tcp,udp} is"
        where
            file :: String
file = Token -> String
onlyLiteralString Token
word
            isNetworked :: Bool
isNetworked = (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
file) ["/dev/tcp", "/dev/udp"]
    bashism (T_Glob id :: Id
id str :: String
str) | Bool -> Bool
not Bool
isDash Bool -> Bool -> Bool
&& "[^" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
str =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3026 "^ in place of ! in glob bracket expressions is"

    bashism t :: Token
t@(TA_Variable id :: Id
id str :: String
str _) | String -> Bool
isBashVariable String
str =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3028 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"

    bashism t :: Token
t@(T_DollarBraced id :: Id
id _ token :: Token
token) = do
        ((Regex, Code, String) -> m ()) -> [(Regex, Code, String)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Regex, Code, String) -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
(Regex, Code, String) -> f ()
check [(Regex, Code, String)]
expansion
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isBashVariable String
var) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3028 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
var String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"
      where
        str :: String
str = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
token
        var :: String
var = String -> String
getBracedReference String
str
        check :: (Regex, Code, String) -> f ()
check (regex :: Regex
regex, code :: Code
code, feature :: String
feature) =
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe [String] -> Bool
forall a. Maybe a -> Bool
isJust (Maybe [String] -> Bool) -> Maybe [String] -> Bool
forall a b. (a -> b) -> a -> b
$ Regex -> String -> Maybe [String]
matchRegex Regex
regex String
str) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id Code
code String
feature

    bashism t :: Token
t@(T_Pipe id :: Id
id "|&") =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3029 "|& in place of 2>&1 | is"
    bashism (T_Array id :: Id
id _) =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3030 "arrays are"
    bashism (T_IoFile id :: Id
id _ t :: Token
t) | Token -> Bool
isGlob Token
t =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3031 "redirecting to/from globs is"
    bashism (T_CoProc id :: Id
id _ _) =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3032 "coproc is"

    bashism (T_Function id :: Id
id _ _ str :: String
str _) | Bool -> Bool
not (String -> Bool
isVariableName String
str) =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3033 "naming functions outside [a-zA-Z_][a-zA-Z0-9_]* is"

    bashism (T_DollarExpansion id :: Id
id [x :: Token
x]) | Token -> Bool
isOnlyRedirection Token
x =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3034 "$(<file) to read files is"
    bashism (T_Backticked id :: Id
id [x :: Token
x]) | Token -> Bool
isOnlyRedirection Token
x =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3035 "`<file` to read files is"

    bashism t :: Token
t@(T_SimpleCommand _ _ (cmd :: Token
cmd:arg :: Token
arg:_))
        | Token
t Token -> String -> Bool
`isCommand` "echo" Bool -> Bool -> Bool
&& String
argString String -> Regex -> Bool
`matches` Regex
flagRegex =
            if Bool
isDash
            then
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
argString String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "-n") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
arg) 3036 "echo flags besides -n"
            else
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
arg) 3037 "echo flags are"
      where
          argString :: String
argString = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
arg
          flagRegex :: Regex
flagRegex = String -> Regex
mkRegex "^-[eEsn]+$"

    bashism t :: Token
t@(T_SimpleCommand _ _ (cmd :: Token
cmd:arg :: Token
arg:_))
        | Token -> Maybe String
getLiteralString Token
cmd Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just "exec" Bool -> Bool -> Bool
&& "-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
arg) =
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
arg) 3038 "exec flags are"
    bashism t :: Token
t@(T_SimpleCommand id :: Id
id _ _)
        | Token
t Token -> String -> Bool
`isCommand` "let" = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3039 "'let' is"
    bashism t :: Token
t@(T_SimpleCommand _ _ (cmd :: Token
cmd:args :: [Token]
args))
        | Token
t Token -> String -> Bool
`isCommand` "set" = Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isDash (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            [(Id, String)] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[(Id, String)] -> m ()
checkOptions ([(Id, String)] -> m ()) -> [(Id, String)] -> m ()
forall a b. (a -> b) -> a -> b
$ [Token] -> [(Id, String)]
getLiteralArgs [Token]
args
      where
        -- Get the literal options from a list of arguments,
        -- up until the first non-literal one
        getLiteralArgs :: [Token] -> [(Id, String)]
        getLiteralArgs :: [Token] -> [(Id, String)]
getLiteralArgs = (Token -> [(Id, String)] -> [(Id, String)])
-> [(Id, String)] -> [Token] -> [(Id, String)]
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Token -> [(Id, String)] -> [(Id, String)]
go []
          where
            go :: Token -> [(Id, String)] -> [(Id, String)]
go first :: Token
first rest :: [(Id, String)]
rest = case Token -> Maybe String
getLiteralString Token
first of
                Just str :: String
str -> (Token -> Id
getId Token
first, String
str) (Id, String) -> [(Id, String)] -> [(Id, String)]
forall a. a -> [a] -> [a]
: [(Id, String)]
rest
                Nothing -> []

        -- Check a flag-option pair (such as -o errexit)
        checkOptions :: [(Id, String)] -> m ()
checkOptions (flag :: (Id, String)
flag@(fid :: Id
fid,flag' :: String
flag') : opt :: (Id, String)
opt@(oid :: Id
oid,opt' :: String
opt') : rest :: [(Id, String)]
rest)
            | String
flag' String -> Regex -> Bool
`matches` Regex
oFlagRegex = do
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
opt' String -> Set String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` Set String
longOptions) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                  Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
oid 3040 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "set option " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
opt' String -> String -> String
forall a. Semigroup a => a -> a -> a
<> " is"
                [(Id, String)] -> m ()
checkFlags ((Id, String)
flag(Id, String) -> [(Id, String)] -> [(Id, String)]
forall a. a -> [a] -> [a]
:[(Id, String)]
rest)
            | Bool
otherwise = [(Id, String)] -> m ()
checkFlags ((Id, String)
flag(Id, String) -> [(Id, String)] -> [(Id, String)]
forall a. a -> [a] -> [a]
:(Id, String)
opt(Id, String) -> [(Id, String)] -> [(Id, String)]
forall a. a -> [a] -> [a]
:[(Id, String)]
rest)
        checkOptions (flag :: (Id, String)
flag:rest :: [(Id, String)]
rest) = [(Id, String)] -> m ()
checkFlags ((Id, String)
flag(Id, String) -> [(Id, String)] -> [(Id, String)]
forall a. a -> [a] -> [a]
:[(Id, String)]
rest)
        checkOptions _           = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

        -- Check that each option in a sequence of flags
        -- (such as -aveo) is valid
        checkFlags :: [(Id, String)] -> m ()
checkFlags (flag :: (Id, String)
flag@(fid :: Id
fid, flag' :: String
flag'):rest :: [(Id, String)]
rest)
            | String -> Bool
startsOption String
flag' = do
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
flag' String -> Regex -> Bool
`matches` Regex
validFlagsRegex) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                  String -> (Char -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (String -> String
forall a. [a] -> [a]
tail String
flag') ((Char -> m ()) -> m ()) -> (Char -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \letter :: Char
letter ->
                    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
letter Char -> Set Char -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` Set Char
optionsSet) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                      Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
fid 3041 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "set flag " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> ('-'Char -> String -> String
forall a. a -> [a] -> [a]
:Char
letterChar -> String -> String
forall a. a -> [a] -> [a]
:" is")
                [(Id, String)] -> m ()
checkOptions [(Id, String)]
rest
            | String -> Bool
beginsWithDoubleDash String
flag' = do
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
fid 3042 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "set flag " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
flag' String -> String -> String
forall a. Semigroup a => a -> a -> a
<> " is"
                [(Id, String)] -> m ()
checkOptions [(Id, String)]
rest
            -- Either a word that doesn't start with a dash, or simply '--',
            -- so stop checking.
            | Bool
otherwise = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        checkFlags [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

        options :: String
options              = "abCefhmnuvxo"
        optionsSet :: Set Char
optionsSet           = String -> Set Char
forall a. Ord a => [a] -> Set a
Set.fromList String
options
        startsOption :: String -> Bool
startsOption         = (String -> Regex -> Bool
`matches` String -> Regex
mkRegex "^(\\+|-[^-])")
        oFlagRegex :: Regex
oFlagRegex           = String -> Regex
mkRegex (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^[-+][" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
options String -> String -> String
forall a. Semigroup a => a -> a -> a
<> "]*o$"
        validFlagsRegex :: Regex
validFlagsRegex      = String -> Regex
mkRegex (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^[-+]([" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
options String -> String -> String
forall a. Semigroup a => a -> a -> a
<> "]+o?|o)$"
        beginsWithDoubleDash :: String -> Bool
beginsWithDoubleDash = (String -> Regex -> Bool
`matches` String -> Regex
mkRegex "^--.+$")
        longOptions :: Set String
longOptions          = [String] -> Set String
forall a. Ord a => [a] -> Set a
Set.fromList
            [ "allexport", "errexit", "ignoreeof", "monitor", "noclobber"
            , "noexec", "noglob", "nolog", "notify" , "nounset", "verbose"
            , "vi", "xtrace" ]

    bashism t :: Token
t@(T_SimpleCommand id :: Id
id _ (cmd :: Token
cmd:rest :: [Token]
rest)) =
        let name :: String
name = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe "" (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getCommandName Token
t
            flags :: [(Token, String)]
flags = Token -> [(Token, String)]
getLeadingFlags Token
t
        in do
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "local" Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
isDash) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                -- This is so commonly accepted that we'll make it a special case
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3043 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "'local' is"
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
unsupportedCommands) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3044 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "'" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ "' is"
            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
                Maybe [String]
allowed' <- String -> Map String (Maybe [String]) -> Maybe (Maybe [String])
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map String (Maybe [String])
allowedFlags
                [String]
allowed <- Maybe [String]
allowed'
                (word :: Token
word, flag :: String
flag) <- ((Token, String) -> Bool)
-> [(Token, String)] -> Maybe (Token, String)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find
                    (\x :: (Token, String)
x -> (Bool -> Bool
not (Bool -> Bool)
-> ((Token, String) -> Bool) -> (Token, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (String -> Bool)
-> ((Token, String) -> String) -> (Token, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, String) -> String
forall a b. (a, b) -> b
snd ((Token, String) -> Bool) -> (Token, String) -> Bool
forall a b. (a -> b) -> a -> b
$ (Token, String)
x) Bool -> Bool -> Bool
&& (Token, String) -> String
forall a b. (a, b) -> b
snd (Token, String)
x String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
allowed) [(Token, String)]
flags
                m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
word) 3045 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " -" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
flag String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"

            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "source") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3046 "'source' in place of '.' is"
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "trap") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                let
                    check :: Token -> m ()
check token :: Token
token = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
                        String
str <- Token -> Maybe String
getLiteralString Token
token
                        let upper :: String
upper = (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toUpper String
str
                        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ do
                            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
upper String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["ERR", "DEBUG", "RETURN"]) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
token) 3047 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "trapping " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is"
                            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ("SIG" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
upper) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
token) 3048
                                    "prefixing signal names with 'SIG' is"
                            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not Bool
isDash Bool -> Bool -> Bool
&& String
upper String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
str) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
token) 3049
                                    "using lower/mixed case for signal names is"
                in
                    (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 (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop 1 [Token]
rest)

            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "printf") (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
format <- [Token]
rest [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! 0  -- flags are covered by allowedFlags
                let literal :: String
literal = Token -> String
onlyLiteralString Token
format
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ "%q" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
literal
                m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg (Token -> Id
getId Token
format) 3050 "printf %q is"
      where
        unsupportedCommands :: [String]
unsupportedCommands = [
            "let", "caller", "builtin", "complete", "compgen", "declare", "dirs", "disown",
            "enable", "mapfile", "readarray", "pushd", "popd", "shopt", "suspend",
            "typeset"
            ]
        allowedFlags :: Map String (Maybe [String])
allowedFlags = [(String, Maybe [String])] -> Map String (Maybe [String])
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [
            ("cd", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["L", "P"]),
            ("exec", [String] -> Maybe [String]
forall a. a -> Maybe a
Just []),
            ("export", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["p"]),
            ("hash", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ([String] -> Maybe [String]) -> [String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ if Bool
isDash then ["r", "v"] else ["r"]),
            ("jobs", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["l", "p"]),
            ("printf", [String] -> Maybe [String]
forall a. a -> Maybe a
Just []),
            ("read", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ([String] -> Maybe [String]) -> [String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ if Bool
isDash then ["r", "p"] else ["r"]),
            ("readonly", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["p"]),
            ("trap", [String] -> Maybe [String]
forall a. a -> Maybe a
Just []),
            ("type", [String] -> Maybe [String]
forall a. a -> Maybe a
Just []),
            ("ulimit", if Bool
isDash then Maybe [String]
forall a. Maybe a
Nothing else [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["f"]),
            ("umask", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["S"]),
            ("unset", [String] -> Maybe [String]
forall a. a -> Maybe a
Just ["f", "v"]),
            ("wait", [String] -> Maybe [String]
forall a. a -> Maybe a
Just [])
            ]
    bashism t :: Token
t@(T_SourceCommand id :: Id
id src :: Token
src _)
        | Token -> Maybe String
getCommandName Token
src Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just "source" = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3051 "'source' in place of '.' is"
    bashism (TA_Expansion _ (T_Literal id :: Id
id str :: String
str : _))
        | String
str String -> Regex -> Bool
`matches` Regex
radix = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warnMsg Id
id 3052 "arithmetic base conversion is"
      where
        radix :: Regex
radix = String -> Regex
mkRegex "^[0-9]+#"
    bashism _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    varChars :: String
varChars="_0-9a-zA-Z"
    expansion :: [(Regex, Code, String)]
expansion = let re :: String -> Regex
re = String -> Regex
mkRegex in [
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^![" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "]", 3053, "indirect expansion is"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "]+\\[.*\\]$", 3054, "array references are"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^![" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "]+\\[[*@]]$", 3055, "array key expansion is"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^![" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "]+[*@]$", 3056, "name matching prefixes are"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "*@]+:[^-=?+]", 3057, "string indexing is"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^([*@][%#]|#[@*])", 3058, "string operations on $@/$* are"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "*@]+(\\[.*\\])?[,^]", 3059, "case modification is"),
        (String -> Regex
re (String -> Regex) -> String -> Regex
forall a b. (a -> b) -> a -> b
$ "^[" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
varChars String -> String -> String
forall a. [a] -> [a] -> [a]
++ "*@]+(\\[.*\\])?/", 3060, "string replacement is")
        ]
    bashVars :: [String]
bashVars = [
        -- This list deliberately excludes $BASH_VERSION as it's often used
        -- for shell identification.
        "OSTYPE", "MACHTYPE", "HOSTTYPE", "HOSTNAME",
        "DIRSTACK", "EUID", "UID", "SHLVL", "PIPESTATUS", "SHELLOPTS",
        "_", "BASHOPTS", "BASHPID", "BASH_ALIASES", "BASH_ARGC",
        "BASH_ARGV", "BASH_ARGV0", "BASH_CMDS", "BASH_COMMAND",
        "BASH_EXECUTION_STRING", "BASH_LINENO", "BASH_REMATCH", "BASH_SOURCE",
        "BASH_SUBSHELL", "BASH_VERSINFO", "EPOCHREALTIME", "EPOCHSECONDS",
        "FUNCNAME", "GROUPS", "MACHTYPE", "MAPFILE"
        ]
    bashDynamicVars :: [String]
bashDynamicVars = [ "RANDOM", "SECONDS" ]
    dashVars :: [String]
dashVars = [ "_" ]
    isBashVariable :: String -> Bool
isBashVariable var :: String
var =
        (String
var String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
bashDynamicVars
            Bool -> Bool -> Bool
|| String
var String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
bashVars Bool -> Bool -> Bool
&& Bool -> Bool
not (String -> Bool
isAssigned String
var))
        Bool -> Bool -> Bool
&& Bool -> Bool
not (Bool
isDash Bool -> Bool -> Bool
&& String
var String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
dashVars)
    isAssigned :: String -> Bool
isAssigned var :: String
var = (StackData -> Bool) -> [StackData] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any StackData -> Bool
f (Parameters -> [StackData]
variableFlow Parameters
params)
      where
        f :: StackData -> Bool
f x :: StackData
x = case StackData
x of
                Assignment (_, _, name :: String
name, _) -> String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
var
                _ -> Bool
False

prop_checkEchoSed1 :: Bool
prop_checkEchoSed1 = ForShell -> String -> Bool
verify ForShell
checkEchoSed "FOO=$(echo \"$cow\" | sed 's/foo/bar/g')"
prop_checkEchoSed1b :: Bool
prop_checkEchoSed1b= ForShell -> String -> Bool
verify ForShell
checkEchoSed "FOO=$(sed 's/foo/bar/g' <<< \"$cow\")"
prop_checkEchoSed2 :: Bool
prop_checkEchoSed2 = ForShell -> String -> Bool
verify ForShell
checkEchoSed "rm $(echo $cow | sed -e 's,foo,bar,')"
prop_checkEchoSed2b :: Bool
prop_checkEchoSed2b= ForShell -> String -> Bool
verify ForShell
checkEchoSed "rm $(sed -e 's,foo,bar,' <<< $cow)"
checkEchoSed :: ForShell
checkEchoSed = [Shell] -> (Token -> Analysis) -> ForShell
ForShell [Shell
Bash, Shell
Ksh] Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> f ()
f (T_Redirecting id :: Id
id lefts :: [Token]
lefts r :: Token
r) =
        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
redirectHereString [Token]
lefts) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> [String] -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> [String] -> f ()
checkSed Id
id [String]
rcmd
      where
        redirectHereString :: Token -> Bool
        redirectHereString :: Token -> Bool
redirectHereString t :: Token
t = case Token
t of
            (T_FdRedirect _ _ T_HereString{}) -> Bool
True
            _                                 -> Bool
False
        rcmd :: [String]
rcmd = Token -> [String]
oversimplify Token
r

    f (T_Pipeline id :: Id
id _ [a :: Token
a, b :: Token
b]) =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([String]
acmd [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== ["echo", "${VAR}"]) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> [String] -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> [String] -> f ()
checkSed Id
id [String]
bcmd
      where
        acmd :: [String]
acmd = Token -> [String]
oversimplify Token
a
        bcmd :: [String]
bcmd = Token -> [String]
oversimplify Token
b

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

    checkSed :: Id -> [String] -> f ()
checkSed id :: Id
id ["sed", v :: String
v]       = Id -> String -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> String -> f ()
checkIn Id
id String
v
    checkSed id :: Id
id ["sed", "-e", v :: String
v] = Id -> String -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> String -> f ()
checkIn Id
id String
v
    checkSed _ _                 = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    -- This should have used backreferences, but TDFA doesn't support them
    sedRe :: Regex
sedRe = String -> Regex
mkRegex "^s(.)([^\n]*)g?$"
    isSimpleSed :: String -> Bool
isSimpleSed s :: String
s = Maybe () -> Bool
forall a. Maybe a -> Bool
isJust (Maybe () -> Bool) -> Maybe () -> Bool
forall a b. (a -> b) -> a -> b
$ do
        [h :: Char
h:_,rest :: String
rest] <- Regex -> String -> Maybe [String]
matchRegex Regex
sedRe String
s
        let delimiters :: String
delimiters = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
h) String
rest
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
delimiters Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 2
    checkIn :: Id -> String -> f ()
checkIn id :: Id
id s :: String
s =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isSimpleSed String
s) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id 2001 "See if you can use ${variable//search/replace} instead."


prop_checkBraceExpansionVars1 :: Bool
prop_checkBraceExpansionVars1 = ForShell -> String -> Bool
verify ForShell
checkBraceExpansionVars "echo {1..$n}"
prop_checkBraceExpansionVars2 :: Bool
prop_checkBraceExpansionVars2 = ForShell -> String -> Bool
verifyNot ForShell
checkBraceExpansionVars "echo {1,3,$n}"
prop_checkBraceExpansionVars3 :: Bool
prop_checkBraceExpansionVars3 = ForShell -> String -> Bool
verify ForShell
checkBraceExpansionVars "eval echo DSC{0001..$n}.jpg"
prop_checkBraceExpansionVars4 :: Bool
prop_checkBraceExpansionVars4 = ForShell -> String -> Bool
verify ForShell
checkBraceExpansionVars "echo {$i..100}"
checkBraceExpansionVars :: ForShell
checkBraceExpansionVars = [Shell] -> (Token -> Analysis) -> ForShell
ForShell [Shell
Bash] Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t@(T_BraceExpansion id :: Id
id list :: [Token]
list) = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
check [Token]
list
      where
        check :: Token -> f ()
check element :: Token
element =
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` Token -> String
toString Token
element) ["$..", "..$"]) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ do
                Bool
c <- Token -> f Bool
forall (m :: * -> *). MonadReader Parameters m => Token -> m Bool
isEvaled Token
element
                if Bool
c
                    then Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id 2175 "Quote this invalid brace expansion since it should be passed literally to eval."
                    else Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id 2051 "Bash doesn't support variables in brace range expansions."
    f _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    literalExt :: Token -> m String
literalExt t :: Token
t =
        case Token
t of
            T_DollarBraced {} -> String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "$"
            T_DollarExpansion {} -> String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "$"
            T_DollarArithmetic {} -> String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "$"
            _ -> String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "-"
    toString :: Token -> String
toString t :: Token
t = Identity String -> String
forall a. Identity a -> a
runIdentity (Identity String -> String) -> Identity String -> String
forall a b. (a -> b) -> a -> b
$ (Token -> Identity String) -> Token -> Identity String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt Token -> Identity String
forall (m :: * -> *). Monad m => Token -> m String
literalExt Token
t
    isEvaled :: Token -> m Bool
isEvaled t :: Token
t = do
        Maybe Token
cmd <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
        Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> m Bool) -> Bool -> m Bool
forall a b. (a -> b) -> a -> b
$ Bool -> (Token -> Bool) -> Maybe Token -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (Token -> String -> Bool
`isUnqualifiedCommand` "eval") Maybe Token
cmd


prop_checkMultiDimensionalArrays1 :: Bool
prop_checkMultiDimensionalArrays1 = ForShell -> String -> Bool
verify ForShell
checkMultiDimensionalArrays "foo[a][b]=3"
prop_checkMultiDimensionalArrays2 :: Bool
prop_checkMultiDimensionalArrays2 = ForShell -> String -> Bool
verifyNot ForShell
checkMultiDimensionalArrays "foo[a]=3"
prop_checkMultiDimensionalArrays3 :: Bool
prop_checkMultiDimensionalArrays3 = ForShell -> String -> Bool
verify ForShell
checkMultiDimensionalArrays "foo=( [a][b]=c )"
prop_checkMultiDimensionalArrays4 :: Bool
prop_checkMultiDimensionalArrays4 = ForShell -> String -> Bool
verifyNot ForShell
checkMultiDimensionalArrays "foo=( [a]=c )"
prop_checkMultiDimensionalArrays5 :: Bool
prop_checkMultiDimensionalArrays5 = ForShell -> String -> Bool
verify ForShell
checkMultiDimensionalArrays "echo ${foo[bar][baz]}"
prop_checkMultiDimensionalArrays6 :: Bool
prop_checkMultiDimensionalArrays6 = ForShell -> String -> Bool
verifyNot ForShell
checkMultiDimensionalArrays "echo ${foo[bar]}"
checkMultiDimensionalArrays :: ForShell
checkMultiDimensionalArrays = [Shell] -> (Token -> Analysis) -> ForShell
ForShell [Shell
Bash] Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f token :: Token
token =
        case Token
token of
            T_Assignment _ _ name :: String
name (first :: Token
first:second :: Token
second:_) _ -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
about Token
second
            T_IndexedElement _ (first :: Token
first:second :: Token
second:_) _ -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
about Token
second
            T_DollarBraced _ _ l :: Token
l ->
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isMultiDim Token
l) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
about Token
token
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    about :: Token -> m ()
about t :: Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2180 "Bash does not support multidimensional arrays. Use 1D or associative arrays."

    re :: Regex
re = String -> Regex
mkRegex "^\\[.*\\]\\[.*\\]"  -- Fixme, this matches ${foo:- [][]} and such as well
    isMultiDim :: Token -> Bool
isMultiDim l :: Token
l = String -> String
getBracedModifier ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l) String -> Regex -> Bool
`matches` Regex
re

prop_checkPS11 :: Bool
prop_checkPS11 = ForShell -> String -> Bool
verify ForShell
checkPS1Assignments "PS1='\\033[1;35m\\$ '"
prop_checkPS11a :: Bool
prop_checkPS11a= ForShell -> String -> Bool
verify ForShell
checkPS1Assignments "export PS1='\\033[1;35m\\$ '"
prop_checkPSf2 :: Bool
prop_checkPSf2 = ForShell -> String -> Bool
verify ForShell
checkPS1Assignments "PS1='\\h \\e[0m\\$ '"
prop_checkPS13 :: Bool
prop_checkPS13 = ForShell -> String -> Bool
verify ForShell
checkPS1Assignments "PS1=$'\\x1b[c '"
prop_checkPS14 :: Bool
prop_checkPS14 = ForShell -> String -> Bool
verify ForShell
checkPS1Assignments "PS1=$'\\e[3m; '"
prop_checkPS14a :: Bool
prop_checkPS14a= ForShell -> String -> Bool
verify ForShell
checkPS1Assignments "export PS1=$'\\e[3m; '"
prop_checkPS15 :: Bool
prop_checkPS15 = ForShell -> String -> Bool
verifyNot ForShell
checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '"
prop_checkPS16 :: Bool
prop_checkPS16 = ForShell -> String -> Bool
verifyNot ForShell
checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '"
prop_checkPS17 :: Bool
prop_checkPS17 = ForShell -> String -> Bool
verifyNot ForShell
checkPS1Assignments "PS1='e033x1B'"
prop_checkPS18 :: Bool
prop_checkPS18 = ForShell -> String -> Bool
verifyNot ForShell
checkPS1Assignments "PS1='\\[\\e\\]'"
checkPS1Assignments :: ForShell
checkPS1Assignments = [Shell] -> (Token -> Analysis) -> ForShell
ForShell [Shell
Bash] Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> f ()
f token :: Token
token = case Token
token of
        (T_Assignment _ _ "PS1" _ word :: Token
word) -> Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnFor Token
word
        _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    warnFor :: Token -> f ()
warnFor word :: Token
word =
        let contents :: String
contents = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
word in
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
containsUnescaped String
contents) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) 2025 "Make sure all escape sequences are enclosed in \\[..\\] to prevent line wrapping issues"
    containsUnescaped :: String -> Bool
containsUnescaped s :: String
s =
        let unenclosed :: String
unenclosed = Regex -> String -> String -> String
subRegex Regex
enclosedRegex String
s "" in
           Maybe [String] -> Bool
forall a. Maybe a -> Bool
isJust (Maybe [String] -> Bool) -> Maybe [String] -> Bool
forall a b. (a -> b) -> a -> b
$ Regex -> String -> Maybe [String]
matchRegex Regex
escapeRegex String
unenclosed
    enclosedRegex :: Regex
enclosedRegex = String -> Regex
mkRegex "\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager
    escapeRegex :: Regex
escapeRegex = String -> Regex
mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033"


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