diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index 117d56f..f6c1cef 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -237,7 +237,7 @@ isCondition (child:parent:rest) = T_UntilExpression id c l -> take 1 . reverse $ c _ -> [] --- prop> verify checkEchoWc "n=$(echo $foo | wc -c)" +-- >>> prop $ verify checkEchoWc "n=$(echo $foo | wc -c)" checkEchoWc _ (T_Pipeline id _ [a, b]) = when (acmd == ["echo", "${VAR}"]) $ case bcmd of @@ -251,21 +251,21 @@ checkEchoWc _ (T_Pipeline id _ [a, b]) = checkEchoWc _ _ = return () -- | --- prop> verify checkPipedAssignment "A=ls | grep foo" --- prop> verifyNot checkPipedAssignment "A=foo cmd | grep foo" --- prop> verifyNot checkPipedAssignment "A=foo" +-- >>> prop $ verify checkPipedAssignment "A=ls | grep foo" +-- >>> prop $ verifyNot checkPipedAssignment "A=foo cmd | grep foo" +-- >>> prop $ verifyNot checkPipedAssignment "A=foo" checkPipedAssignment _ (T_Pipeline _ _ (T_Redirecting _ _ (T_SimpleCommand id (_:_) []):_:_)) = warn id 2036 "If you wanted to assign the output of the pipeline, use a=$(b | c) ." checkPipedAssignment _ _ = return () -- | --- prop> verify checkAssignAteCommand "A=ls -l" --- prop> verify checkAssignAteCommand "A=ls --sort=$foo" --- prop> verify checkAssignAteCommand "A=cat foo | grep bar" --- prop> verifyNot checkAssignAteCommand "A=foo ls -l" --- prop> verify checkAssignAteCommand "PAGER=cat grep bar" --- prop> verifyNot checkAssignAteCommand "PAGER=\"cat\" grep bar" --- prop> verify checkAssignAteCommand "here=pwd" +-- >>> prop $ verify checkAssignAteCommand "A=ls -l" +-- >>> prop $ verify checkAssignAteCommand "A=ls --sort=$foo" +-- >>> prop $ verify checkAssignAteCommand "A=cat foo | grep bar" +-- >>> prop $ verifyNot checkAssignAteCommand "A=foo ls -l" +-- >>> prop $ verify checkAssignAteCommand "PAGER=cat grep bar" +-- >>> prop $ verifyNot checkAssignAteCommand "PAGER=\"cat\" grep bar" +-- >>> prop $ verify checkAssignAteCommand "here=pwd" checkAssignAteCommand _ (T_SimpleCommand id (T_Assignment _ _ _ _ assignmentTerm:[]) list) = -- Check if first word is intended as an argument (flag or glob). if firstWordIsArg list @@ -285,9 +285,9 @@ checkAssignAteCommand _ (T_SimpleCommand id (T_Assignment _ _ _ _ assignmentTerm checkAssignAteCommand _ _ = return () -- | --- prop> verify checkArithmeticOpCommand "i=i + 1" --- prop> verify checkArithmeticOpCommand "foo=bar * 2" --- prop> verifyNot checkArithmeticOpCommand "foo + opts" +-- >>> prop $ verify checkArithmeticOpCommand "i=i + 1" +-- >>> prop $ verify checkArithmeticOpCommand "foo=bar * 2" +-- >>> prop $ verifyNot checkArithmeticOpCommand "foo + opts" checkArithmeticOpCommand _ (T_SimpleCommand id [T_Assignment {}] (firstWord:_)) = fromMaybe (return ()) $ check <$> getGlobOrLiteralString firstWord where @@ -298,8 +298,8 @@ checkArithmeticOpCommand _ (T_SimpleCommand id [T_Assignment {}] (firstWord:_)) checkArithmeticOpCommand _ _ = return () -- | --- prop> verify checkWrongArithmeticAssignment "i=i+1" --- prop> verify checkWrongArithmeticAssignment "n=2; i=n*2" +-- >>> prop $ verify checkWrongArithmeticAssignment "i=i+1" +-- >>> prop $ verify checkWrongArithmeticAssignment "n=2; i=n*2" checkWrongArithmeticAssignment params (T_SimpleCommand id (T_Assignment _ _ _ _ val:[]) []) = fromMaybe (return ()) $ do str <- getNormalString val @@ -328,12 +328,12 @@ checkWrongArithmeticAssignment _ _ = return () -- | --- prop> verify checkUuoc "cat foo | grep bar" --- prop> verifyNot checkUuoc "cat * | grep bar" --- prop> verify checkUuoc "cat $var | grep bar" --- prop> verifyNot checkUuoc "cat $var" --- prop> verifyNot checkUuoc "cat \"$@\"" --- prop> verifyNot checkUuoc "cat -n | grep bar" +-- >>> prop $ verify checkUuoc "cat foo | grep bar" +-- >>> prop $ verifyNot checkUuoc "cat * | grep bar" +-- >>> prop $ verify checkUuoc "cat $var | grep bar" +-- >>> prop $ verifyNot checkUuoc "cat $var" +-- >>> prop $ verifyNot checkUuoc "cat \"$@\"" +-- >>> prop $ verifyNot checkUuoc "cat -n | grep bar" checkUuoc _ (T_Pipeline _ _ (T_Redirecting _ _ cmd:_:_)) = checkCommand "cat" (const f) cmd where @@ -344,20 +344,20 @@ checkUuoc _ (T_Pipeline _ _ (T_Redirecting _ _ cmd:_:_)) = checkUuoc _ _ = return () -- | --- prop> verify checkPipePitfalls "ls | grep -v mp3" --- prop> verifyNot checkPipePitfalls "find . -print0 | xargs -0 foo" --- prop> verifyNot checkPipePitfalls "ls -N | foo" --- prop> verify checkPipePitfalls "find . | xargs foo" --- prop> verifyNot checkPipePitfalls "find . -printf '%s\\n' | xargs foo" --- prop> verify checkPipePitfalls "foo | grep bar | wc -l" --- prop> verifyNot checkPipePitfalls "foo | grep -o bar | wc -l" --- prop> verifyNot checkPipePitfalls "foo | grep -o bar | wc" --- prop> verifyNot checkPipePitfalls "foo | grep bar | wc" --- prop> verifyNot checkPipePitfalls "foo | grep -o bar | wc -c" --- prop> verifyNot checkPipePitfalls "foo | grep bar | wc -c" --- prop> verifyNot checkPipePitfalls "foo | grep -o bar | wc -cmwL" --- prop> verifyNot checkPipePitfalls "foo | grep bar | wc -cmwL" --- prop> verifyNot checkPipePitfalls "foo | grep -r bar | wc -l" +-- >>> prop $ verify checkPipePitfalls "ls | grep -v mp3" +-- >>> prop $ verifyNot checkPipePitfalls "find . -print0 | xargs -0 foo" +-- >>> prop $ verifyNot checkPipePitfalls "ls -N | foo" +-- >>> prop $ verify checkPipePitfalls "find . | xargs foo" +-- >>> prop $ verifyNot checkPipePitfalls "find . -printf '%s\\n' | xargs foo" +-- >>> prop $ verify checkPipePitfalls "foo | grep bar | wc -l" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep -o bar | wc -l" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep -o bar | wc" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep bar | wc" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep -o bar | wc -c" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep bar | wc -c" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep -o bar | wc -cmwL" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep bar | wc -cmwL" +-- >>> prop $ verifyNot checkPipePitfalls "foo | grep -r bar | wc -l" checkPipePitfalls _ (T_Pipeline id _ commands) = do for ["find", "xargs"] $ \(find:xargs:_) -> @@ -422,20 +422,20 @@ indexOfSublists sub = f 0 -- | --- prop> verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow" --- prop> verifyNotTree checkShebangParameters "#! /bin/sh -l " +-- >>> prop $ verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow" +-- >>> prop $ verifyNotTree checkShebangParameters "#! /bin/sh -l " checkShebangParameters p (T_Annotation _ _ t) = checkShebangParameters p t checkShebangParameters _ (T_Script id sb _) = [makeComment ErrorC id 2096 "On most OS, shebangs can only specify a single parameter." | length (words sb) > 2] -- | --- prop> verifyNotTree checkShebang "#!/usr/bin/env bash -x\necho cow" --- prop> verifyNotTree checkShebang "#! /bin/sh -l " --- prop> verifyTree checkShebang "ls -l" --- prop> verifyNotTree checkShebang "#shellcheck shell=sh\nfoo" --- prop> verifyTree checkShebang "#!/usr/bin/env ash" --- prop> verifyNotTree checkShebang "#!/usr/bin/env ash\n# shellcheck shell=dash\n" --- prop> verifyNotTree checkShebang "#!/usr/bin/env ash\n# shellcheck shell=sh\n" +-- >>> prop $ verifyNotTree checkShebang "#!/usr/bin/env bash -x\necho cow" +-- >>> prop $ verifyNotTree checkShebang "#! /bin/sh -l " +-- >>> prop $ verifyTree checkShebang "ls -l" +-- >>> prop $ verifyNotTree checkShebang "#shellcheck shell=sh\nfoo" +-- >>> prop $ verifyTree checkShebang "#!/usr/bin/env ash" +-- >>> prop $ verifyNotTree checkShebang "#!/usr/bin/env ash\n# shellcheck shell=dash\n" +-- >>> prop $ verifyNotTree checkShebang "#!/usr/bin/env ash\n# shellcheck shell=sh\n" checkShebang params (T_Annotation _ list t) = if any isOverride list then [] else checkShebang params t where @@ -450,15 +450,15 @@ checkShebang params (T_Script id sb _) = execWriter $ -- | --- prop> verify checkForInQuoted "for f in \"$(ls)\"; do echo foo; done" --- prop> verifyNot checkForInQuoted "for f in \"$@\"; do echo foo; done" --- prop> verifyNot checkForInQuoted "for f in *.mp3; do echo foo; done" --- prop> verify checkForInQuoted "for f in \"*.mp3\"; do echo foo; done" --- prop> verify checkForInQuoted "for f in 'find /'; do true; done" --- prop> verify checkForInQuoted "for f in 1,2,3; do true; done" --- prop> verifyNot checkForInQuoted "for f in foo{1,2,3}; do true; done" --- prop> verify checkForInQuoted "for f in ls; do true; done" --- prop> verifyNot checkForInQuoted "for f in \"${!arr}\"; do true; done" +-- >>> prop $ verify checkForInQuoted "for f in \"$(ls)\"; do echo foo; done" +-- >>> prop $ verifyNot checkForInQuoted "for f in \"$@\"; do echo foo; done" +-- >>> prop $ verifyNot checkForInQuoted "for f in *.mp3; do echo foo; done" +-- >>> prop $ verify checkForInQuoted "for f in \"*.mp3\"; do echo foo; done" +-- >>> prop $ verify checkForInQuoted "for f in 'find /'; do true; done" +-- >>> prop $ verify checkForInQuoted "for f in 1,2,3; do true; done" +-- >>> prop $ verifyNot checkForInQuoted "for f in foo{1,2,3}; do true; done" +-- >>> prop $ verify checkForInQuoted "for f in ls; do true; done" +-- >>> prop $ verifyNot checkForInQuoted "for f in \"${!arr}\"; do true; done" checkForInQuoted _ (T_ForIn _ f [T_NormalWord _ [word@(T_DoubleQuoted id list)]] _) = when (any (\x -> willSplit x && not (mayBecomeMultipleArgs x)) list || (fmap wouldHaveBeenGlob (getLiteralString word) == Just True)) $ @@ -473,11 +473,11 @@ checkForInQuoted _ (T_ForIn _ f [T_NormalWord _ [T_Literal id s]] _) = checkForInQuoted _ _ = return () -- | --- prop> verify checkForInCat "for f in $(cat foo); do stuff; done" --- prop> verify checkForInCat "for f in `cat foo`; do stuff; done" --- prop> verify checkForInCat "for f in $(cat foo | grep lol); do stuff; done" --- prop> verify checkForInCat "for f in `cat foo | grep lol`; do stuff; done" --- prop> verifyNot checkForInCat "for f in $(cat foo | grep bar | wc -l); do stuff; done" +-- >>> prop $ verify checkForInCat "for f in $(cat foo); do stuff; done" +-- >>> prop $ verify checkForInCat "for f in `cat foo`; do stuff; done" +-- >>> prop $ verify checkForInCat "for f in $(cat foo | grep lol); do stuff; done" +-- >>> prop $ verify checkForInCat "for f in `cat foo | grep lol`; do stuff; done" +-- >>> prop $ verifyNot checkForInCat "for f in $(cat foo | grep bar | wc -l); do stuff; done" checkForInCat _ (T_ForIn _ f [T_NormalWord _ w] _) = mapM_ checkF w where checkF (T_DollarExpansion id [T_Pipeline _ _ r]) @@ -490,9 +490,9 @@ checkForInCat _ (T_ForIn _ f [T_NormalWord _ w] _) = mapM_ checkF w checkForInCat _ _ = return () -- | --- prop> verify checkForInLs "for f in $(ls *.mp3); do mplayer \"$f\"; done" --- prop> verify checkForInLs "for f in `ls *.mp3`; do mplayer \"$f\"; done" --- prop> verify checkForInLs "for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done" +-- >>> prop $ verify checkForInLs "for f in $(ls *.mp3); do mplayer \"$f\"; done" +-- >>> prop $ verify checkForInLs "for f in `ls *.mp3`; do mplayer \"$f\"; done" +-- >>> prop $ verify checkForInLs "for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done" checkForInLs _ = try where try (T_ForIn _ f [T_NormalWord _ [T_DollarExpansion id [x]]] _) = @@ -510,12 +510,12 @@ checkForInLs _ = try -- | --- prop> verify checkFindExec "find / -name '*.php' -exec rm {};" --- prop> verify checkFindExec "find / -exec touch {} && ls {} \\;" --- prop> verify checkFindExec "find / -execdir cat {} | grep lol +" --- prop> verifyNot checkFindExec "find / -name '*.php' -exec foo {} +" --- prop> verifyNot checkFindExec "find / -execdir bash -c 'a && b' \\;" --- prop> verify checkFindExec "find / -type d -execdir rm *.jpg \\;" +-- >>> prop $ verify checkFindExec "find / -name '*.php' -exec rm {};" +-- >>> prop $ verify checkFindExec "find / -exec touch {} && ls {} \\;" +-- >>> prop $ verify checkFindExec "find / -execdir cat {} | grep lol +" +-- >>> prop $ verifyNot checkFindExec "find / -name '*.php' -exec foo {} +" +-- >>> prop $ verifyNot checkFindExec "find / -execdir bash -c 'a && b' \\;" +-- >>> prop $ verify checkFindExec "find / -type d -execdir rm *.jpg \\;" checkFindExec _ cmd@(T_SimpleCommand _ _ t@(h:r)) | cmd `isCommand` "find" = do c <- broken r False when c $ @@ -551,17 +551,17 @@ checkFindExec _ _ = return () -- | --- prop> verify checkUnquotedExpansions "rm $(ls)" --- prop> verify checkUnquotedExpansions "rm `ls`" --- prop> verify checkUnquotedExpansions "rm foo$(date)" --- prop> verify checkUnquotedExpansions "[ $(foo) == cow ]" --- prop> verify checkUnquotedExpansions "[ ! $(foo) ]" --- prop> verifyNot checkUnquotedExpansions "[[ $(foo) == cow ]]" --- prop> verifyNot checkUnquotedExpansions "for f in $(cmd); do echo $f; done" --- prop> verifyNot checkUnquotedExpansions "$(cmd)" --- prop> verifyNot checkUnquotedExpansions "cat << foo\n$(ls)\nfoo" --- prop> verifyNot checkUnquotedExpansions "set -- $(seq 1 4)" --- prop> verifyNot checkUnquotedExpansions "echo foo `# inline comment`" +-- >>> prop $ verify checkUnquotedExpansions "rm $(ls)" +-- >>> prop $ verify checkUnquotedExpansions "rm `ls`" +-- >>> prop $ verify checkUnquotedExpansions "rm foo$(date)" +-- >>> prop $ verify checkUnquotedExpansions "[ $(foo) == cow ]" +-- >>> prop $ verify checkUnquotedExpansions "[ ! $(foo) ]" +-- >>> prop $ verifyNot checkUnquotedExpansions "[[ $(foo) == cow ]]" +-- >>> prop $ verifyNot checkUnquotedExpansions "for f in $(cmd); do echo $f; done" +-- >>> prop $ verifyNot checkUnquotedExpansions "$(cmd)" +-- >>> prop $ verifyNot checkUnquotedExpansions "cat << foo\n$(ls)\nfoo" +-- >>> prop $ verifyNot checkUnquotedExpansions "set -- $(seq 1 4)" +-- >>> prop $ verifyNot checkUnquotedExpansions "echo foo `# inline comment`" checkUnquotedExpansions params = check where @@ -579,14 +579,14 @@ checkUnquotedExpansions params = -- | --- prop> verify checkRedirectToSame "cat foo > foo" --- prop> verify checkRedirectToSame "cat lol | sed -e 's/a/b/g' > lol" --- prop> verifyNot checkRedirectToSame "cat lol | sed -e 's/a/b/g' > foo.bar && mv foo.bar lol" --- prop> verifyNot checkRedirectToSame "foo /dev/null > /dev/null" --- prop> verifyNot checkRedirectToSame "foo > bar 2> bar" --- prop> verifyNot checkRedirectToSame "echo foo > foo" --- prop> verifyNot checkRedirectToSame "sed 's/foo/bar/g' file | sponge file" --- prop> verifyNot checkRedirectToSame "while read -r line; do _=\"$fname\"; done <\"$fname\"" +-- >>> prop $ verify checkRedirectToSame "cat foo > foo" +-- >>> prop $ verify checkRedirectToSame "cat lol | sed -e 's/a/b/g' > lol" +-- >>> prop $ verifyNot checkRedirectToSame "cat lol | sed -e 's/a/b/g' > foo.bar && mv foo.bar lol" +-- >>> prop $ verifyNot checkRedirectToSame "foo /dev/null > /dev/null" +-- >>> prop $ verifyNot checkRedirectToSame "foo > bar 2> bar" +-- >>> prop $ verifyNot checkRedirectToSame "echo foo > foo" +-- >>> prop $ verifyNot checkRedirectToSame "sed 's/foo/bar/g' file | sponge file" +-- >>> prop $ verifyNot checkRedirectToSame "while read -r line; do _=\"$fname\"; done <\"$fname\"" checkRedirectToSame params s@(T_Pipeline _ _ list) = mapM_ (\l -> (mapM_ (\x -> doAnalysis (checkOccurrences x) l) (getAllRedirs list))) list where @@ -633,14 +633,14 @@ checkRedirectToSame _ _ = return () -- | --- prop> verify checkShorthandIf "[[ ! -z file ]] && scp file host || rm file" --- prop> verifyNot checkShorthandIf "[[ ! -z file ]] && { scp file host || echo 'Eek'; }" --- prop> verifyNot checkShorthandIf "foo && bar || echo baz" --- prop> verifyNot checkShorthandIf "foo && a=b || a=c" --- prop> verifyNot checkShorthandIf "foo && rm || printf b" --- prop> verifyNot checkShorthandIf "if foo && bar || baz; then true; fi" --- prop> verifyNot checkShorthandIf "while foo && bar || baz; do true; done" --- prop> verify checkShorthandIf "if true; then foo && bar || baz; fi" +-- >>> prop $ verify checkShorthandIf "[[ ! -z file ]] && scp file host || rm file" +-- >>> prop $ verifyNot checkShorthandIf "[[ ! -z file ]] && { scp file host || echo 'Eek'; }" +-- >>> prop $ verifyNot checkShorthandIf "foo && bar || echo baz" +-- >>> prop $ verifyNot checkShorthandIf "foo && a=b || a=c" +-- >>> prop $ verifyNot checkShorthandIf "foo && rm || printf b" +-- >>> prop $ verifyNot checkShorthandIf "if foo && bar || baz; then true; fi" +-- >>> prop $ verifyNot checkShorthandIf "while foo && bar || baz; do true; done" +-- >>> prop $ verify checkShorthandIf "if true; then foo && bar || baz; fi" checkShorthandIf params x@(T_AndIf id _ (T_OrIf _ _ (T_Pipeline _ _ t))) | not (isOk t || inCondition) = info id 2015 "Note that A && B || C is not if-then-else. C may run when A is true." @@ -654,9 +654,9 @@ checkShorthandIf _ _ = return () -- | --- prop> verify checkDollarStar "for f in $*; do ..; done" --- prop> verifyNot checkDollarStar "a=$*" --- prop> verifyNot checkDollarStar "[[ $* = 'a b' ]]" +-- >>> prop $ verify checkDollarStar "for f in $*; do ..; done" +-- >>> prop $ verifyNot checkDollarStar "a=$*" +-- >>> prop $ verifyNot checkDollarStar "[[ $* = 'a b' ]]" checkDollarStar p t@(T_NormalWord _ [b@(T_DollarBraced id _)]) | bracedString b == "*" = unless (isStrictlyQuoteFree (parentMap p) t) $ @@ -665,17 +665,17 @@ checkDollarStar _ _ = return () -- | --- prop> verify checkUnquotedDollarAt "ls $@" --- prop> verifyNot checkUnquotedDollarAt "ls ${#@}" --- prop> verify checkUnquotedDollarAt "ls ${foo[@]}" --- prop> verifyNot checkUnquotedDollarAt "ls ${#foo[@]}" --- prop> verifyNot checkUnquotedDollarAt "ls \"$@\"" --- prop> verifyNot checkUnquotedDollarAt "ls ${foo/@/ at }" --- prop> verifyNot checkUnquotedDollarAt "a=$@" --- prop> verify checkUnquotedDollarAt "for f in ${var[@]}; do true; done" --- prop> verifyNot checkUnquotedDollarAt "echo \"${args[@]:+${args[@]}}\"" --- prop> verifyNot checkUnquotedDollarAt "echo ${args[@]:+\"${args[@]}\"}" --- prop> verifyNot checkUnquotedDollarAt "echo ${@+\"$@\"}" +-- >>> prop $ verify checkUnquotedDollarAt "ls $@" +-- >>> prop $ verifyNot checkUnquotedDollarAt "ls ${#@}" +-- >>> prop $ verify checkUnquotedDollarAt "ls ${foo[@]}" +-- >>> prop $ verifyNot checkUnquotedDollarAt "ls ${#foo[@]}" +-- >>> prop $ verifyNot checkUnquotedDollarAt "ls \"$@\"" +-- >>> prop $ verifyNot checkUnquotedDollarAt "ls ${foo/@/ at }" +-- >>> prop $ verifyNot checkUnquotedDollarAt "a=$@" +-- >>> prop $ verify checkUnquotedDollarAt "for f in ${var[@]}; do true; done" +-- >>> prop $ verifyNot checkUnquotedDollarAt "echo \"${args[@]:+${args[@]}}\"" +-- >>> prop $ verifyNot checkUnquotedDollarAt "echo ${args[@]:+\"${args[@]}\"}" +-- >>> prop $ verifyNot checkUnquotedDollarAt "echo ${@+\"$@\"}" checkUnquotedDollarAt p word@(T_NormalWord _ parts) | not $ isStrictlyQuoteFree (parentMap p) word = forM_ (take 1 $ filter isArrayExpansion parts) $ \x -> unless (isQuotedAlternativeReference x) $ @@ -684,11 +684,11 @@ checkUnquotedDollarAt p word@(T_NormalWord _ parts) | not $ isStrictlyQuoteFree checkUnquotedDollarAt _ _ = return () -- | --- prop> verify checkConcatenatedDollarAt "echo \"foo$@\"" --- prop> verify checkConcatenatedDollarAt "echo ${arr[@]}lol" --- prop> verify checkConcatenatedDollarAt "echo $a$@" --- prop> verifyNot checkConcatenatedDollarAt "echo $@" --- prop> verifyNot checkConcatenatedDollarAt "echo \"${arr[@]}\"" +-- >>> prop $ verify checkConcatenatedDollarAt "echo \"foo$@\"" +-- >>> prop $ verify checkConcatenatedDollarAt "echo ${arr[@]}lol" +-- >>> prop $ verify checkConcatenatedDollarAt "echo $a$@" +-- >>> prop $ verifyNot checkConcatenatedDollarAt "echo $@" +-- >>> prop $ verifyNot checkConcatenatedDollarAt "echo \"${arr[@]}\"" checkConcatenatedDollarAt p word@T_NormalWord {} | not $ isQuoteFree (parentMap p) word = unless (null $ drop 1 parts) $ @@ -700,13 +700,13 @@ checkConcatenatedDollarAt p word@T_NormalWord {} checkConcatenatedDollarAt _ _ = return () -- | --- prop> verify checkArrayAsString "a=$@" --- prop> verify checkArrayAsString "a=\"${arr[@]}\"" --- prop> verify checkArrayAsString "a=*.png" --- prop> verify checkArrayAsString "a={1..10}" --- prop> verifyNot checkArrayAsString "a='*.gif'" --- prop> verifyNot checkArrayAsString "a=$*" --- prop> verifyNot checkArrayAsString "a=( $@ )" +-- >>> prop $ verify checkArrayAsString "a=$@" +-- >>> prop $ verify checkArrayAsString "a=\"${arr[@]}\"" +-- >>> prop $ verify checkArrayAsString "a=*.png" +-- >>> prop $ verify checkArrayAsString "a={1..10}" +-- >>> prop $ verifyNot checkArrayAsString "a='*.gif'" +-- >>> prop $ verifyNot checkArrayAsString "a=$*" +-- >>> prop $ verifyNot checkArrayAsString "a=( $@ )" checkArrayAsString _ (T_Assignment id _ _ _ word) = if willConcatInAssignment word then @@ -719,14 +719,14 @@ checkArrayAsString _ (T_Assignment id _ _ _ word) = checkArrayAsString _ _ = return () -- | --- prop> verifyTree checkArrayWithoutIndex "foo=(a b); echo $foo" --- prop> verifyNotTree checkArrayWithoutIndex "foo='bar baz'; foo=($foo); echo ${foo[0]}" --- prop> verifyTree checkArrayWithoutIndex "coproc foo while true; do echo cow; done; echo $foo" --- prop> verifyTree checkArrayWithoutIndex "coproc tail -f log; echo $COPROC" --- prop> verifyTree checkArrayWithoutIndex "a[0]=foo; echo $a" --- prop> verifyTree checkArrayWithoutIndex "echo $PIPESTATUS" --- prop> verifyTree checkArrayWithoutIndex "a=(a b); a+=c" --- prop> verifyTree checkArrayWithoutIndex "declare -a foo; foo=bar;" +-- >>> prop $ verifyTree checkArrayWithoutIndex "foo=(a b); echo $foo" +-- >>> prop $ verifyNotTree checkArrayWithoutIndex "foo='bar baz'; foo=($foo); echo ${foo[0]}" +-- >>> prop $ verifyTree checkArrayWithoutIndex "coproc foo while true; do echo cow; done; echo $foo" +-- >>> prop $ verifyTree checkArrayWithoutIndex "coproc tail -f log; echo $COPROC" +-- >>> prop $ verifyTree checkArrayWithoutIndex "a[0]=foo; echo $a" +-- >>> prop $ verifyTree checkArrayWithoutIndex "echo $PIPESTATUS" +-- >>> prop $ verifyTree checkArrayWithoutIndex "a=(a b); a+=c" +-- >>> prop $ verifyTree checkArrayWithoutIndex "declare -a foo; foo=bar;" checkArrayWithoutIndex params _ = doVariableFlowAnalysis readF writeF defaultMap (variableFlow params) where @@ -762,13 +762,13 @@ checkArrayWithoutIndex params _ = _ -> False -- | --- prop> verify checkStderrRedirect "test 2>&1 > cow" --- prop> verifyNot checkStderrRedirect "test > cow 2>&1" --- prop> verifyNot checkStderrRedirect "test 2>&1 > file | grep stderr" --- prop> verifyNot checkStderrRedirect "errors=$(test 2>&1 > file)" --- prop> verifyNot checkStderrRedirect "read < <(test 2>&1 > file)" --- prop> verify checkStderrRedirect "foo | bar 2>&1 > /dev/null" --- prop> verifyNot checkStderrRedirect "{ cmd > file; } 2>&1" +-- >>> prop $ verify checkStderrRedirect "test 2>&1 > cow" +-- >>> prop $ verifyNot checkStderrRedirect "test > cow 2>&1" +-- >>> prop $ verifyNot checkStderrRedirect "test 2>&1 > file | grep stderr" +-- >>> prop $ verifyNot checkStderrRedirect "errors=$(test 2>&1 > file)" +-- >>> prop $ verifyNot checkStderrRedirect "read < <(test 2>&1 > file)" +-- >>> prop $ verify checkStderrRedirect "foo | bar 2>&1 > /dev/null" +-- >>> prop $ verifyNot checkStderrRedirect "{ cmd > file; } 2>&1" checkStderrRedirect params redir@(T_Redirecting _ [ T_FdRedirect id "2" (T_IoDuplicate _ (T_GREATAND _) "1"), T_FdRedirect _ _ (T_IoFile _ op _) @@ -796,27 +796,27 @@ ltt t = trace ("Tracing " ++ show t) -- | --- prop> verify checkSingleQuotedVariables "echo '$foo'" --- prop> verify checkSingleQuotedVariables "echo 'lol$1.jpg'" --- prop> verifyNot checkSingleQuotedVariables "sed 's/foo$/bar/'" --- prop> verify checkSingleQuotedVariables "sed 's/${foo}/bar/'" --- prop> verify checkSingleQuotedVariables "sed 's/$(echo cow)/bar/'" --- prop> verify checkSingleQuotedVariables "sed 's/$((1+foo))/bar/'" --- prop> verifyNot checkSingleQuotedVariables "awk '{print $1}'" --- prop> verifyNot checkSingleQuotedVariables "trap 'echo $SECONDS' EXIT" --- prop> verifyNot checkSingleQuotedVariables "sed -n '$p'" --- prop> verify checkSingleQuotedVariables "sed -n '$pattern'" --- prop> verifyNot checkSingleQuotedVariables "PS1='$PWD \\$ '" --- prop> verify checkSingleQuotedVariables "find . -exec echo '$1' {} +" --- prop> verifyNot checkSingleQuotedVariables "find . -exec awk '{print $1}' {} \\;" --- prop> verify checkSingleQuotedVariables "echo '`pwd`'" --- prop> verifyNot checkSingleQuotedVariables "sed '${/lol/d}'" --- prop> verifyNot checkSingleQuotedVariables "eval 'echo $1'" --- prop> verifyNot checkSingleQuotedVariables "busybox awk '{print $1}'" --- prop> verifyNot checkSingleQuotedVariables "[ -v 'bar[$foo]' ]" --- prop> verifyNot checkSingleQuotedVariables "git filter-branch 'test $GIT_COMMIT'" --- prop> verify checkSingleQuotedVariables "git '$a'" --- prop> verifyNot checkSingleQuotedVariables "rename 's/(.)a/$1/g' *" +-- >>> prop $ verify checkSingleQuotedVariables "echo '$foo'" +-- >>> prop $ verify checkSingleQuotedVariables "echo 'lol$1.jpg'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "sed 's/foo$/bar/'" +-- >>> prop $ verify checkSingleQuotedVariables "sed 's/${foo}/bar/'" +-- >>> prop $ verify checkSingleQuotedVariables "sed 's/$(echo cow)/bar/'" +-- >>> prop $ verify checkSingleQuotedVariables "sed 's/$((1+foo))/bar/'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "awk '{print $1}'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "trap 'echo $SECONDS' EXIT" +-- >>> prop $ verifyNot checkSingleQuotedVariables "sed -n '$p'" +-- >>> prop $ verify checkSingleQuotedVariables "sed -n '$pattern'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "PS1='$PWD \\$ '" +-- >>> prop $ verify checkSingleQuotedVariables "find . -exec echo '$1' {} +" +-- >>> prop $ verifyNot checkSingleQuotedVariables "find . -exec awk '{print $1}' {} \\;" +-- >>> prop $ verify checkSingleQuotedVariables "echo '`pwd`'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "sed '${/lol/d}'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "eval 'echo $1'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "busybox awk '{print $1}'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "[ -v 'bar[$foo]' ]" +-- >>> prop $ verifyNot checkSingleQuotedVariables "git filter-branch 'test $GIT_COMMIT'" +-- >>> prop $ verify checkSingleQuotedVariables "git '$a'" +-- >>> prop $ verifyNot checkSingleQuotedVariables "rename 's/(.)a/$1/g' *" checkSingleQuotedVariables params t@(T_SingleQuoted id s) = when (s `matches` re) $ @@ -884,30 +884,30 @@ checkSingleQuotedVariables _ _ = return () -- | --- prop> verify checkUnquotedN "if [ -n $foo ]; then echo cow; fi" --- prop> verify checkUnquotedN "[ -n $cow ]" --- prop> verifyNot checkUnquotedN "[[ -n $foo ]] && echo cow" --- prop> verify checkUnquotedN "[ -n $cow -o -t 1 ]" --- prop> verifyNot checkUnquotedN "[ -n \"$@\" ]" +-- >>> prop $ verify checkUnquotedN "if [ -n $foo ]; then echo cow; fi" +-- >>> prop $ verify checkUnquotedN "[ -n $cow ]" +-- >>> prop $ verifyNot checkUnquotedN "[[ -n $foo ]] && echo cow" +-- >>> prop $ verify checkUnquotedN "[ -n $cow -o -t 1 ]" +-- >>> prop $ verifyNot checkUnquotedN "[ -n \"$@\" ]" checkUnquotedN _ (TC_Unary _ SingleBracket "-n" (T_NormalWord id [t])) | willSplit t = err id 2070 "-n doesn't work with unquoted arguments. Quote or use [[ ]]." checkUnquotedN _ _ = return () -- | --- prop> verify checkNumberComparisons "[[ $foo < 3 ]]" --- prop> verify checkNumberComparisons "[[ 0 >= $(cmd) ]]" --- prop> verifyNot checkNumberComparisons "[[ $foo ]] > 3" --- prop> verify checkNumberComparisons "[[ $foo > 2.72 ]]" --- prop> verify checkNumberComparisons "[[ $foo -le 2.72 ]]" --- prop> verify checkNumberComparisons "[[ 3.14 -eq $foo ]]" --- prop> verifyNot checkNumberComparisons "[[ 3.14 == $foo ]]" --- prop> verify checkNumberComparisons "[ foo <= bar ]" --- prop> verify checkNumberComparisons "[ foo \\>= bar ]" --- prop> verify checkNumberComparisons "[ $foo -eq 'N' ]" --- prop> verify checkNumberComparisons "[ x$foo -gt x${N} ]" --- prop> verify checkNumberComparisons "[ $foo > $bar ]" --- prop> verifyNot checkNumberComparisons "[[ foo < bar ]]" --- prop> verifyNot checkNumberComparisons "[ $foo '>' $bar ]" +-- >>> prop $ verify checkNumberComparisons "[[ $foo < 3 ]]" +-- >>> prop $ verify checkNumberComparisons "[[ 0 >= $(cmd) ]]" +-- >>> prop $ verifyNot checkNumberComparisons "[[ $foo ]] > 3" +-- >>> prop $ verify checkNumberComparisons "[[ $foo > 2.72 ]]" +-- >>> prop $ verify checkNumberComparisons "[[ $foo -le 2.72 ]]" +-- >>> prop $ verify checkNumberComparisons "[[ 3.14 -eq $foo ]]" +-- >>> prop $ verifyNot checkNumberComparisons "[[ 3.14 == $foo ]]" +-- >>> prop $ verify checkNumberComparisons "[ foo <= bar ]" +-- >>> prop $ verify checkNumberComparisons "[ foo \\>= bar ]" +-- >>> prop $ verify checkNumberComparisons "[ $foo -eq 'N' ]" +-- >>> prop $ verify checkNumberComparisons "[ x$foo -gt x${N} ]" +-- >>> prop $ verify checkNumberComparisons "[ $foo > $bar ]" +-- >>> prop $ verifyNot checkNumberComparisons "[[ foo < bar ]]" +-- >>> prop $ verifyNot checkNumberComparisons "[ $foo '>' $bar ]" checkNumberComparisons params (TC_Binary id typ op lhs rhs) = do if isNum lhs || isNum rhs then do @@ -991,26 +991,26 @@ checkNumberComparisons params (TC_Binary id typ op lhs rhs) = do checkNumberComparisons _ _ = return () -- | --- prop> verify checkSingleBracketOperators "[ test =~ foo ]" +-- >>> prop $ verify checkSingleBracketOperators "[ test =~ foo ]" checkSingleBracketOperators params (TC_Binary id SingleBracket "=~" lhs rhs) = when (shellType params `elem` [Bash, Ksh]) $ err id 2074 $ "Can't use =~ in [ ]. Use [[..]] instead." checkSingleBracketOperators _ _ = return () -- | --- prop> verify checkDoubleBracketOperators "[[ 3 \\< 4 ]]" --- prop> verifyNot checkDoubleBracketOperators "[[ foo < bar ]]" +-- >>> prop $ verify checkDoubleBracketOperators "[[ 3 \\< 4 ]]" +-- >>> prop $ verifyNot checkDoubleBracketOperators "[[ foo < bar ]]" checkDoubleBracketOperators _ x@(TC_Binary id typ op lhs rhs) | typ == DoubleBracket && op `elem` ["\\<", "\\>"] = err id 2075 $ "Escaping " ++ op ++" is required in [..], but invalid in [[..]]" checkDoubleBracketOperators _ _ = return () -- | --- prop> verify checkConditionalAndOrs "[ foo && bar ]" --- prop> verify checkConditionalAndOrs "[[ foo -o bar ]]" --- prop> verifyNot checkConditionalAndOrs "[[ foo || bar ]]" --- prop> verify checkConditionalAndOrs "[ foo -a bar ]" --- prop> verify checkConditionalAndOrs "[ -z 3 -o a = b ]" +-- >>> prop $ verify checkConditionalAndOrs "[ foo && bar ]" +-- >>> prop $ verify checkConditionalAndOrs "[[ foo -o bar ]]" +-- >>> prop $ verifyNot checkConditionalAndOrs "[[ foo || bar ]]" +-- >>> prop $ verify checkConditionalAndOrs "[ foo -a bar ]" +-- >>> prop $ verify checkConditionalAndOrs "[ -z 3 -o a = b ]" checkConditionalAndOrs _ t = case t of (TC_And id SingleBracket "&&" _ _) -> @@ -1030,12 +1030,12 @@ checkConditionalAndOrs _ t = _ -> return () -- | --- prop> verify checkQuotedCondRegex "[[ $foo =~ \"bar.*\" ]]" --- prop> verify checkQuotedCondRegex "[[ $foo =~ '(cow|bar)' ]]" --- prop> verifyNot checkQuotedCondRegex "[[ $foo =~ $foo ]]" --- prop> verifyNot checkQuotedCondRegex "[[ $foo =~ \"bar\" ]]" --- prop> verifyNot checkQuotedCondRegex "[[ $foo =~ 'cow bar' ]]" --- prop> verify checkQuotedCondRegex "[[ $foo =~ 'cow|bar' ]]" +-- >>> prop $ verify checkQuotedCondRegex "[[ $foo =~ \"bar.*\" ]]" +-- >>> prop $ verify checkQuotedCondRegex "[[ $foo =~ '(cow|bar)' ]]" +-- >>> prop $ verifyNot checkQuotedCondRegex "[[ $foo =~ $foo ]]" +-- >>> prop $ verifyNot checkQuotedCondRegex "[[ $foo =~ \"bar\" ]]" +-- >>> prop $ verifyNot checkQuotedCondRegex "[[ $foo =~ 'cow bar' ]]" +-- >>> prop $ verify checkQuotedCondRegex "[[ $foo =~ 'cow|bar' ]]" checkQuotedCondRegex _ (TC_Binary _ _ "=~" _ rhs) = case rhs of T_NormalWord id [T_DoubleQuoted _ _] -> error rhs @@ -1054,11 +1054,11 @@ checkQuotedCondRegex _ (TC_Binary _ _ "=~" _ rhs) = checkQuotedCondRegex _ _ = return () -- | --- prop> verify checkGlobbedRegex "[[ $foo =~ *foo* ]]" --- prop> verify checkGlobbedRegex "[[ $foo =~ f* ]]" --- prop> verify checkGlobbedRegex "[[ $foo =~ \\#* ]]" --- prop> verifyNot checkGlobbedRegex "[[ $foo =~ $foo ]]" --- prop> verifyNot checkGlobbedRegex "[[ $foo =~ ^c.* ]]" +-- >>> prop $ verify checkGlobbedRegex "[[ $foo =~ *foo* ]]" +-- >>> prop $ verify checkGlobbedRegex "[[ $foo =~ f* ]]" +-- >>> prop $ verify checkGlobbedRegex "[[ $foo =~ \\#* ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ $foo ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ ^c.* ]]" checkGlobbedRegex _ (TC_Binary _ DoubleBracket "=~" _ rhs) = let s = concat $ oversimplify rhs in when (isConfusedGlobRegex s) $ @@ -1067,16 +1067,16 @@ checkGlobbedRegex _ _ = return () -- | --- prop> verify checkConstantIfs "[[ foo != bar ]]" --- prop> verify checkConstantIfs "[ n -le 4 ]" --- prop> verifyNot checkConstantIfs "[[ n -le 4 ]]" --- prop> verify checkConstantIfs "[[ $n -le 4 && n != 2 ]]" --- prop> verifyNot checkConstantIfs "[[ $n -le 3 ]]" --- prop> verifyNot checkConstantIfs "[[ $n -le $n ]]" --- prop> verifyNot checkConstantIfs "[[ a -ot b ]]" --- prop> verifyNot checkConstantIfs "[ a -nt b ]" --- prop> verifyNot checkConstantIfs "[[ ~foo == '~foo' ]]" --- prop> verify checkConstantIfs "[[ *.png == [a-z] ]]" +-- >>> prop $ verify checkConstantIfs "[[ foo != bar ]]" +-- >>> prop $ verify checkConstantIfs "[ n -le 4 ]" +-- >>> prop $ verifyNot checkConstantIfs "[[ n -le 4 ]]" +-- >>> prop $ verify checkConstantIfs "[[ $n -le 4 && n != 2 ]]" +-- >>> prop $ verifyNot checkConstantIfs "[[ $n -le 3 ]]" +-- >>> prop $ verifyNot checkConstantIfs "[[ $n -le $n ]]" +-- >>> prop $ verifyNot checkConstantIfs "[[ a -ot b ]]" +-- >>> prop $ verifyNot checkConstantIfs "[ a -nt b ]" +-- >>> prop $ verifyNot checkConstantIfs "[[ ~foo == '~foo' ]]" +-- >>> prop $ verify checkConstantIfs "[[ *.png == [a-z] ]]" checkConstantIfs _ (TC_Binary id typ op lhs rhs) | not isDynamic = if isConstant lhs && isConstant rhs then warn id 2050 "This expression is constant. Did you forget the $ on a variable?" @@ -1093,15 +1093,15 @@ checkConstantIfs _ (TC_Binary id typ op lhs rhs) | not isDynamic = checkConstantIfs _ _ = return () -- | --- prop> verify checkLiteralBreakingTest "[[ a==$foo ]]" --- prop> verify checkLiteralBreakingTest "[ $foo=3 ]" --- prop> verify checkLiteralBreakingTest "[ $foo!=3 ]" --- prop> verify checkLiteralBreakingTest "[ \"$(ls) \" ]" --- prop> verify checkLiteralBreakingTest "[ -n \"$(true) \" ]" --- prop> verify checkLiteralBreakingTest "[ -z $(true)z ]" --- prop> verifyNot checkLiteralBreakingTest "[ -z $(true) ]" --- prop> verifyNot checkLiteralBreakingTest "[ $(true)$(true) ]" --- prop> verify checkLiteralBreakingTest "[ -z foo ]" +-- >>> prop $ verify checkLiteralBreakingTest "[[ a==$foo ]]" +-- >>> prop $ verify checkLiteralBreakingTest "[ $foo=3 ]" +-- >>> prop $ verify checkLiteralBreakingTest "[ $foo!=3 ]" +-- >>> prop $ verify checkLiteralBreakingTest "[ \"$(ls) \" ]" +-- >>> prop $ verify checkLiteralBreakingTest "[ -n \"$(true) \" ]" +-- >>> prop $ verify checkLiteralBreakingTest "[ -z $(true)z ]" +-- >>> prop $ verifyNot checkLiteralBreakingTest "[ -z $(true) ]" +-- >>> prop $ verifyNot checkLiteralBreakingTest "[ $(true)$(true) ]" +-- >>> prop $ verify checkLiteralBreakingTest "[ -z foo ]" checkLiteralBreakingTest _ t = potentially $ case t of (TC_Nullary _ _ w@(T_NormalWord _ l)) -> do @@ -1129,13 +1129,13 @@ checkLiteralBreakingTest _ t = potentially $ return $ err (getId token) 2157 s -- | --- prop> verify checkConstantNullary "[[ '$(foo)' ]]" --- prop> verify checkConstantNullary "[ \"-f lol\" ]" --- prop> verify checkConstantNullary "[[ cmd ]]" --- prop> verify checkConstantNullary "[[ ! cmd ]]" --- prop> verify checkConstantNullary "[[ true ]]" --- prop> verify checkConstantNullary "[ 1 ]" --- prop> verify checkConstantNullary "[ false ]" +-- >>> prop $ verify checkConstantNullary "[[ '$(foo)' ]]" +-- >>> prop $ verify checkConstantNullary "[ \"-f lol\" ]" +-- >>> prop $ verify checkConstantNullary "[[ cmd ]]" +-- >>> prop $ verify checkConstantNullary "[[ ! cmd ]]" +-- >>> prop $ verify checkConstantNullary "[[ true ]]" +-- >>> prop $ verify checkConstantNullary "[ 1 ]" +-- >>> prop $ verify checkConstantNullary "[ false ]" checkConstantNullary _ (TC_Nullary _ _ t) | isConstant t = case fromMaybe "" $ getLiteralString t of "false" -> err (getId t) 2158 "[ false ] is true. Remove the brackets." @@ -1149,9 +1149,9 @@ checkConstantNullary _ (TC_Nullary _ _ t) | isConstant t = checkConstantNullary _ _ = return () -- | --- prop> verify checkForDecimals "((3.14*c))" --- prop> verify checkForDecimals "foo[1.2]=bar" --- prop> verifyNot checkForDecimals "declare -A foo; foo[1.2]=bar" +-- >>> prop $ verify checkForDecimals "((3.14*c))" +-- >>> prop $ verify checkForDecimals "foo[1.2]=bar" +-- >>> prop $ verifyNot checkForDecimals "declare -A foo; foo[1.2]=bar" checkForDecimals params t@(TA_Expansion id _) = potentially $ do guard $ not (hasFloatingPoint params) str <- getLiteralString t @@ -1161,30 +1161,30 @@ checkForDecimals params t@(TA_Expansion id _) = potentially $ do checkForDecimals _ _ = return () -- | --- prop> verify checkDivBeforeMult "echo $((c/n*100))" --- prop> verifyNot checkDivBeforeMult "echo $((c*100/n))" --- prop> verifyNot checkDivBeforeMult "echo $((c/10*10))" +-- >>> prop $ verify checkDivBeforeMult "echo $((c/n*100))" +-- >>> prop $ verifyNot checkDivBeforeMult "echo $((c*100/n))" +-- >>> prop $ verifyNot checkDivBeforeMult "echo $((c/10*10))" checkDivBeforeMult params (TA_Binary _ "*" (TA_Binary id "/" _ x) y) | not (hasFloatingPoint params) && x /= y = info id 2017 "Increase precision by replacing a/b*c with a*c/b." checkDivBeforeMult _ _ = return () -- | --- prop> verify checkArithmeticDeref "echo $((3+$foo))" --- prop> verify checkArithmeticDeref "cow=14; (( s+= $cow ))" --- prop> verifyNot checkArithmeticDeref "cow=1/40; (( s+= ${cow%%/*} ))" --- prop> verifyNot checkArithmeticDeref "(( ! $? ))" --- prop> verifyNot checkArithmeticDeref "(($1))" --- prop> verify checkArithmeticDeref "(( a[$i] ))" --- prop> verifyNot checkArithmeticDeref "(( 10#$n ))" --- prop> verifyNot checkArithmeticDeref "let i=$i+1" --- prop> verifyNot checkArithmeticDeref "(( a[foo] ))" --- prop> verifyNot checkArithmeticDeref "(( a[\\$foo] ))" --- prop> verifyNot checkArithmeticDeref "a[$foo]=wee" --- prop> verify checkArithmeticDeref "for ((i=0; $i < 3; i)); do true; done" --- prop> verifyNot checkArithmeticDeref "(( $$ ))" --- prop> verifyNot checkArithmeticDeref "(( $! ))" --- prop> verifyNot checkArithmeticDeref "(( ${!var} ))" +-- >>> prop $ verify checkArithmeticDeref "echo $((3+$foo))" +-- >>> prop $ verify checkArithmeticDeref "cow=14; (( s+= $cow ))" +-- >>> prop $ verifyNot checkArithmeticDeref "cow=1/40; (( s+= ${cow%%/*} ))" +-- >>> prop $ verifyNot checkArithmeticDeref "(( ! $? ))" +-- >>> prop $ verifyNot checkArithmeticDeref "(($1))" +-- >>> prop $ verify checkArithmeticDeref "(( a[$i] ))" +-- >>> prop $ verifyNot checkArithmeticDeref "(( 10#$n ))" +-- >>> prop $ verifyNot checkArithmeticDeref "let i=$i+1" +-- >>> prop $ verifyNot checkArithmeticDeref "(( a[foo] ))" +-- >>> prop $ verifyNot checkArithmeticDeref "(( a[\\$foo] ))" +-- >>> prop $ verifyNot checkArithmeticDeref "a[$foo]=wee" +-- >>> prop $ verify checkArithmeticDeref "for ((i=0; $i < 3; i)); do true; done" +-- >>> prop $ verifyNot checkArithmeticDeref "(( $$ ))" +-- >>> prop $ verifyNot checkArithmeticDeref "(( $! ))" +-- >>> prop $ verifyNot checkArithmeticDeref "(( ${!var} ))" checkArithmeticDeref params t@(TA_Expansion _ [b@(T_DollarBraced id _)]) = unless (isException $ bracedString b) getWarning where @@ -1204,9 +1204,9 @@ checkArithmeticDeref params t@(TA_Expansion _ [b@(T_DollarBraced id _)]) = checkArithmeticDeref _ _ = return () -- | --- prop> verify checkArithmeticBadOctal "(( 0192 ))" --- prop> verifyNot checkArithmeticBadOctal "(( 0x192 ))" --- prop> verifyNot checkArithmeticBadOctal "(( 1 ^ 0777 ))" +-- >>> prop $ verify checkArithmeticBadOctal "(( 0192 ))" +-- >>> prop $ verifyNot checkArithmeticBadOctal "(( 0x192 ))" +-- >>> prop $ verifyNot checkArithmeticBadOctal "(( 1 ^ 0777 ))" checkArithmeticBadOctal _ t@(TA_Expansion id _) = potentially $ do str <- getLiteralString t guard $ str `matches` octalRE @@ -1216,11 +1216,11 @@ checkArithmeticBadOctal _ t@(TA_Expansion id _) = potentially $ do checkArithmeticBadOctal _ _ = return () -- | --- prop> verify checkComparisonAgainstGlob "[[ $cow == $bar ]]" --- prop> verifyNot checkComparisonAgainstGlob "[[ $cow == \"$bar\" ]]" --- prop> verify checkComparisonAgainstGlob "[ $cow = *foo* ]" --- prop> verifyNot checkComparisonAgainstGlob "[ $cow = foo ]" --- prop> verify checkComparisonAgainstGlob "[[ $cow != $bar ]]" +-- >>> prop $ verify checkComparisonAgainstGlob "[[ $cow == $bar ]]" +-- >>> prop $ verifyNot checkComparisonAgainstGlob "[[ $cow == \"$bar\" ]]" +-- >>> prop $ verify checkComparisonAgainstGlob "[ $cow = *foo* ]" +-- >>> prop $ verifyNot checkComparisonAgainstGlob "[ $cow = foo ]" +-- >>> prop $ verify checkComparisonAgainstGlob "[[ $cow != $bar ]]" checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op _ (T_NormalWord id [T_DollarBraced _ _])) | op `elem` ["=", "==", "!="] = warn id 2053 $ "Quote the rhs of " ++ op ++ " in [[ ]] to prevent glob matching." @@ -1230,12 +1230,12 @@ checkComparisonAgainstGlob _ (TC_Binary _ SingleBracket op _ word) checkComparisonAgainstGlob _ _ = return () -- | --- prop> verify checkCommarrays "a=(1, 2)" --- prop> verify checkCommarrays "a+=(1,2,3)" --- prop> verifyNot checkCommarrays "cow=(1 \"foo,bar\" 3)" --- prop> verifyNot checkCommarrays "cow=('one,' 'two')" --- prop> verify checkCommarrays "a=([a]=b, [c]=d)" --- prop> verify checkCommarrays "a=([a]=b,[c]=d,[e]=f)" +-- >>> prop $ verify checkCommarrays "a=(1, 2)" +-- >>> prop $ verify checkCommarrays "a+=(1,2,3)" +-- >>> prop $ verifyNot checkCommarrays "cow=(1 \"foo,bar\" 3)" +-- >>> prop $ verifyNot checkCommarrays "cow=('one,' 'two')" +-- >>> prop $ verify checkCommarrays "a=([a]=b, [c]=d)" +-- >>> prop $ verify checkCommarrays "a=([a]=b,[c]=d,[e]=f)" checkCommarrays _ (T_Array id l) = when (any (isCommaSeparated . literal) l) $ warn id 2054 "Use spaces, not commas, to separate array elements." @@ -1249,11 +1249,12 @@ checkCommarrays _ (T_Array id l) = checkCommarrays _ _ = return () -- | --- prop> verify checkOrNeq "if [[ $lol -ne cow || $lol -ne foo ]]; then echo foo; fi" --- prop> verify checkOrNeq "(( a!=lol || a!=foo ))" --- prop> verify checkOrNeq "[ \"$a\" != lol || \"$a\" != foo ]" --- prop> verifyNot checkOrNeq "[ a != $cow || b != $foo ]" --- prop> verifyNot checkOrNeq "[[ $a != /home || $a != */public_html/* ]]" +-- >>> prop $ verify checkOrNeq "if [[ $lol -ne cow || $lol -ne foo ]]; then echo foo; fi" +-- >>> prop $ verify checkOrNeq "(( a!=lol || a!=foo ))" +-- >>> prop $ verify checkOrNeq "[ \"$a\" != lol || \"$a\" != foo ]" +-- >>> prop $ verifyNot checkOrNeq "[ a != $cow || b != $foo ]" +-- >>> prop $ verifyNot checkOrNeq "[[ $a != /home || $a != */public_html/* ]]" +-- -- This only catches the most idiomatic cases. Fixme? checkOrNeq _ (TC_Or id typ op (TC_Binary _ _ op1 lhs1 rhs1 ) (TC_Binary _ _ op2 lhs2 rhs2)) | lhs1 == lhs2 && (op1 == op2 && (op1 == "-ne" || op1 == "!=")) && not (any isGlob [rhs1,rhs2]) = @@ -1266,11 +1267,11 @@ checkOrNeq _ _ = return () -- | --- prop> verify checkValidCondOps "[[ a -xz b ]]" --- prop> verify checkValidCondOps "[ -M a ]" --- prop> verifyNot checkValidCondOps "[ 3 \\> 2 ]" --- prop> verifyNot checkValidCondOps "[ 1 = 2 -a 3 -ge 4 ]" --- prop> verifyNot checkValidCondOps "[[ ! -v foo ]]" +-- >>> prop $ verify checkValidCondOps "[[ a -xz b ]]" +-- >>> prop $ verify checkValidCondOps "[ -M a ]" +-- >>> prop $ verifyNot checkValidCondOps "[ 3 \\> 2 ]" +-- >>> prop $ verifyNot checkValidCondOps "[ 1 = 2 -a 3 -ge 4 ]" +-- >>> prop $ verifyNot checkValidCondOps "[[ ! -v foo ]]" checkValidCondOps _ (TC_Binary id _ s _ _) | s `notElem` binaryTestOps = warn id 2057 "Unknown binary operator." @@ -1280,15 +1281,15 @@ checkValidCondOps _ (TC_Unary id _ s _) checkValidCondOps _ _ = return () -- | --- prop> verify checkUuoeVar "for f in $(echo $tmp); do echo lol; done" --- prop> verify checkUuoeVar "date +`echo \"$format\"`" --- prop> verifyNot checkUuoeVar "foo \"$(echo -e '\r')\"" --- prop> verifyNot checkUuoeVar "echo $tmp" --- prop> verify checkUuoeVar "foo \"$(echo \"$(date) value:\" $value)\"" --- prop> verifyNot checkUuoeVar "foo \"$(echo files: *.png)\"" --- prop> verifyNot checkUuoeVar "foo $(echo $(bar))" -- covered by 2005 --- prop> verifyNot checkUuoeVar "#!/bin/sh\nz=$(echo)" --- prop> verify checkUuoeVar "foo $(echo $(>> prop $ verify checkUuoeVar "for f in $(echo $tmp); do echo lol; done" +-- >>> prop $ verify checkUuoeVar "date +`echo \"$format\"`" +-- >>> prop $ verifyNot checkUuoeVar "foo \"$(echo -e '\r')\"" +-- >>> prop $ verifyNot checkUuoeVar "echo $tmp" +-- >>> prop $ verify checkUuoeVar "foo \"$(echo \"$(date) value:\" $value)\"" +-- >>> prop $ verifyNot checkUuoeVar "foo \"$(echo files: *.png)\"" +-- >>> prop $ verifyNot checkUuoeVar "foo $(echo $(bar))" -- covered by 2005 +-- >>> prop $ verifyNot checkUuoeVar "#!/bin/sh\nz=$(echo)" +-- >>> prop $ verify checkUuoeVar "foo $(echo $( check id cmd @@ -1316,10 +1317,10 @@ checkUuoeVar _ p = -- | --- prop> verify checkTestRedirects "test 3 > 1" --- prop> verifyNot checkTestRedirects "test 3 \\> 1" --- prop> verify checkTestRedirects "/usr/bin/test $var > $foo" --- prop> verifyNot checkTestRedirects "test 1 -eq 2 2> file" +-- >>> prop $ verify checkTestRedirects "test 3 > 1" +-- >>> prop $ verifyNot checkTestRedirects "test 3 \\> 1" +-- >>> prop $ verify checkTestRedirects "/usr/bin/test $var > $foo" +-- >>> prop $ verifyNot checkTestRedirects "test 1 -eq 2 2> file" checkTestRedirects _ (T_Redirecting id redirs cmd) | cmd `isCommand` "test" = mapM_ check redirs where @@ -1338,16 +1339,16 @@ checkTestRedirects _ (T_Redirecting id redirs cmd) | cmd `isCommand` "test" = checkTestRedirects _ _ = return () -- | --- prop> verify checkPS1Assignments "PS1='\\033[1;35m\\$ '" --- prop> verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '" --- prop> verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" --- prop> verify checkPS1Assignments "PS1=$'\\x1b[c '" --- prop> verify checkPS1Assignments "PS1=$'\\e[3m; '" --- prop> verify checkPS1Assignments "export PS1=$'\\e[3m; '" --- prop> verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" --- prop> verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" --- prop> verifyNot checkPS1Assignments "PS1='e033x1B'" --- prop> verifyNot checkPS1Assignments "PS1='\\[\\e\\]'" +-- >>> prop $ verify checkPS1Assignments "PS1='\\033[1;35m\\$ '" +-- >>> prop $ verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '" +-- >>> prop $ verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" +-- >>> prop $ verify checkPS1Assignments "PS1=$'\\x1b[c '" +-- >>> prop $ verify checkPS1Assignments "PS1=$'\\e[3m; '" +-- >>> prop $ verify checkPS1Assignments "export PS1=$'\\e[3m; '" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='e033x1B'" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\e\\]'" checkPS1Assignments _ (T_Assignment _ _ "PS1" _ word) = warnFor word where warnFor word = @@ -1361,19 +1362,19 @@ checkPS1Assignments _ (T_Assignment _ _ "PS1" _ word) = warnFor word escapeRegex = mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033" checkPS1Assignments _ _ = return () --- prop> verify checkBackticks "echo `foo`" --- prop> verifyNot checkBackticks "echo $(foo)" --- prop> verifyNot checkBackticks "echo `#inlined comment` foo" +-- >>> prop $ verify checkBackticks "echo `foo`" +-- >>> prop $ verifyNot checkBackticks "echo $(foo)" +-- >>> prop $ verifyNot checkBackticks "echo `#inlined comment` foo" checkBackticks _ (T_Backticked id list) | not (null list) = style id 2006 "Use $(...) notation instead of legacy backticked `...`." checkBackticks _ _ = return () -- | --- prop> verify checkIndirectExpansion "${foo$n}" --- prop> verifyNot checkIndirectExpansion "${foo//$n/lol}" --- prop> verify checkIndirectExpansion "${$#}" --- prop> verify checkIndirectExpansion "${var${n}_$((i%2))}" --- prop> verifyNot checkIndirectExpansion "${bar}" +-- >>> prop $ verify checkIndirectExpansion "${foo$n}" +-- >>> prop $ verifyNot checkIndirectExpansion "${foo//$n/lol}" +-- >>> prop $ verify checkIndirectExpansion "${$#}" +-- >>> prop $ verify checkIndirectExpansion "${var${n}_$((i%2))}" +-- >>> prop $ verifyNot checkIndirectExpansion "${bar}" checkIndirectExpansion _ (T_DollarBraced i (T_NormalWord _ contents)) = when (isIndirection contents) $ err i 2082 "To expand via indirection, use arrays, ${!name} or (for sh only) eval." @@ -1394,14 +1395,14 @@ checkIndirectExpansion _ (T_DollarBraced i (T_NormalWord _ contents)) = checkIndirectExpansion _ _ = return () -- | --- prop> verify checkInexplicablyUnquoted "echo 'var='value';'" --- prop> verifyNot checkInexplicablyUnquoted "'foo'*" --- prop> verifyNot checkInexplicablyUnquoted "wget --user-agent='something'" --- prop> verify checkInexplicablyUnquoted "echo \"VALUES (\"id\")\"" --- prop> verifyNot checkInexplicablyUnquoted "\"$dir\"/\"$file\"" --- prop> verifyNot checkInexplicablyUnquoted "\"$dir\"some_stuff\"$file\"" --- prop> verifyNot checkInexplicablyUnquoted "${dir/\"foo\"/\"bar\"}" --- prop> verifyNot checkInexplicablyUnquoted " 'foo'\\\n 'bar'" +-- >>> prop $ verify checkInexplicablyUnquoted "echo 'var='value';'" +-- >>> prop $ verifyNot checkInexplicablyUnquoted "'foo'*" +-- >>> prop $ verifyNot checkInexplicablyUnquoted "wget --user-agent='something'" +-- >>> prop $ verify checkInexplicablyUnquoted "echo \"VALUES (\"id\")\"" +-- >>> prop $ verifyNot checkInexplicablyUnquoted "\"$dir\"/\"$file\"" +-- >>> prop $ verifyNot checkInexplicablyUnquoted "\"$dir\"some_stuff\"$file\"" +-- >>> prop $ verifyNot checkInexplicablyUnquoted "${dir/\"foo\"/\"bar\"}" +-- >>> prop $ verifyNot checkInexplicablyUnquoted " 'foo'\\\n 'bar'" checkInexplicablyUnquoted _ (T_NormalWord id tokens) = mapM_ check (tails tokens) where check (T_SingleQuoted _ _:T_Literal id str:_) @@ -1434,11 +1435,11 @@ checkInexplicablyUnquoted _ (T_NormalWord id tokens) = mapM_ check (tails tokens checkInexplicablyUnquoted _ _ = return () -- | --- prop> verify checkTildeInQuotes "var=\"~/out.txt\"" --- prop> verify checkTildeInQuotes "foo > '~/dir'" --- prop> verifyNot checkTildeInQuotes "~/file" --- prop> verifyNot checkTildeInQuotes "echo '/~foo/cow'" --- prop> verifyNot checkTildeInQuotes "awk '$0 ~ /foo/'" +-- >>> prop $ verify checkTildeInQuotes "var=\"~/out.txt\"" +-- >>> prop $ verify checkTildeInQuotes "foo > '~/dir'" +-- >>> prop $ verifyNot checkTildeInQuotes "~/file" +-- >>> prop $ verifyNot checkTildeInQuotes "echo '/~foo/cow'" +-- >>> prop $ verifyNot checkTildeInQuotes "awk '$0 ~ /foo/'" checkTildeInQuotes _ = check where verify id ('~':'/':_) = warn id 2088 "Tilde does not expand in quotes. Use $HOME." @@ -1449,8 +1450,8 @@ checkTildeInQuotes _ = check verify id str check _ = return () --- prop> verify checkLonelyDotDash "./ file" --- prop> verifyNot checkLonelyDotDash "./file" +-- >>> prop $ verify checkLonelyDotDash "./ file" +-- >>> prop $ verifyNot checkLonelyDotDash "./file" checkLonelyDotDash _ t@(T_Redirecting id _ _) | isUnqualifiedCommand t "./" = err id 2083 "Don't add spaces after the slash in './file'." @@ -1458,14 +1459,14 @@ checkLonelyDotDash _ _ = return () -- | --- prop> verify checkSpuriousExec "exec foo; true" --- prop> verify checkSpuriousExec "if a; then exec b; exec c; fi" --- prop> verifyNot checkSpuriousExec "echo cow; exec foo" --- prop> verifyNot checkSpuriousExec "if a; then exec b; fi" --- prop> verifyNot checkSpuriousExec "exec > file; cmd" --- prop> verify checkSpuriousExec "exec foo > file; cmd" --- prop> verifyNot checkSpuriousExec "exec file; echo failed; exit 3" --- prop> verifyNot checkSpuriousExec "exec {origout}>&1- >tmp.log 2>&1; bar" +-- >>> prop $ verify checkSpuriousExec "exec foo; true" +-- >>> prop $ verify checkSpuriousExec "if a; then exec b; exec c; fi" +-- >>> prop $ verifyNot checkSpuriousExec "echo cow; exec foo" +-- >>> prop $ verifyNot checkSpuriousExec "if a; then exec b; fi" +-- >>> prop $ verifyNot checkSpuriousExec "exec > file; cmd" +-- >>> prop $ verify checkSpuriousExec "exec foo > file; cmd" +-- >>> prop $ verifyNot checkSpuriousExec "exec file; echo failed; exit 3" +-- >>> prop $ verifyNot checkSpuriousExec "exec {origout}>&1- >tmp.log 2>&1; bar" checkSpuriousExec _ = doLists where doLists (T_Script _ _ cmds) = doList cmds @@ -1501,10 +1502,10 @@ checkSpuriousExec _ = doLists -- | --- prop> verify checkSpuriousExpansion "if $(true); then true; fi" --- prop> verify checkSpuriousExpansion "while \"$(cmd)\"; do :; done" --- prop> verifyNot checkSpuriousExpansion "$(cmd) --flag1 --flag2" --- prop> verify checkSpuriousExpansion "$((i++))" +-- >>> prop $ verify checkSpuriousExpansion "if $(true); then true; fi" +-- >>> prop $ verify checkSpuriousExpansion "while \"$(cmd)\"; do :; done" +-- >>> prop $ verifyNot checkSpuriousExpansion "$(cmd) --flag1 --flag2" +-- >>> prop $ verify checkSpuriousExpansion "$((i++))" checkSpuriousExpansion _ (T_SimpleCommand _ _ [T_NormalWord _ [word]]) = check word where check word = case word of @@ -1520,15 +1521,15 @@ checkSpuriousExpansion _ _ = return () -- | --- prop> verify checkDollarBrackets "echo $[1+2]" --- prop> verifyNot checkDollarBrackets "echo $((1+2))" +-- >>> prop $ verify checkDollarBrackets "echo $[1+2]" +-- >>> prop $ verifyNot checkDollarBrackets "echo $((1+2))" checkDollarBrackets _ (T_DollarBracket id _) = style id 2007 "Use $((..)) instead of deprecated $[..]" checkDollarBrackets _ _ = return () -- | --- prop> verify checkSshHereDoc "ssh host << foo\necho $PATH\nfoo" --- prop> verifyNot checkSshHereDoc "ssh host << 'foo'\necho $PATH\nfoo" +-- >>> prop $ verify checkSshHereDoc "ssh host << foo\necho $PATH\nfoo" +-- >>> prop $ verifyNot checkSshHereDoc "ssh host << 'foo'\necho $PATH\nfoo" checkSshHereDoc _ (T_Redirecting _ redirs cmd) | cmd `isCommand` "ssh" = mapM_ checkHereDoc redirs @@ -1542,26 +1543,26 @@ checkSshHereDoc _ _ = return () -- | Subshell detection. -- --- prop> verifyTree subshellAssignmentCheck "cat foo | while read bar; do a=$bar; done; echo \"$a\"" --- prop> verifyNotTree subshellAssignmentCheck "while read bar; do a=$bar; done < file; echo \"$a\"" --- prop> verifyTree subshellAssignmentCheck "( A=foo; ); rm $A" --- prop> verifyNotTree subshellAssignmentCheck "( A=foo; rm $A; )" --- prop> verifyTree subshellAssignmentCheck "cat foo | while read cow; do true; done; echo $cow;" --- prop> verifyTree subshellAssignmentCheck "( export lol=$(ls); ); echo $lol;" --- prop> verifyTree subshellAssignmentCheck "( typeset -a lol=a; ); echo $lol;" --- prop> verifyTree subshellAssignmentCheck "cmd | while read foo; do (( n++ )); done; echo \"$n lines\"" --- prop> verifyTree subshellAssignmentCheck "n=3 & echo $((n++))" --- prop> verifyTree subshellAssignmentCheck "read n & n=foo$n" --- prop> verifyTree subshellAssignmentCheck "(( n <<= 3 )) & (( n |= 4 )) &" --- prop> verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let n=n+1; done\necho $n" --- prop> verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let ++n; done\necho $n" --- prop> verifyTree subshellAssignmentCheck "#!/bin/bash\necho foo | read bar; echo $bar" --- prop> verifyNotTree subshellAssignmentCheck "#!/bin/ksh93\necho foo | read bar; echo $bar" --- prop> verifyNotTree subshellAssignmentCheck "#!/bin/ksh\ncat foo | while read bar; do a=$bar; done\necho \"$a\"" --- prop> verifyNotTree subshellAssignmentCheck "(set -e); echo $@" --- prop> verifyNotTree subshellAssignmentCheck "foo=${ { bar=$(baz); } 2>&1; }; echo $foo $bar" --- prop> verifyTree subshellAssignmentCheck "( exec {n}>&2; ); echo $n" --- prop> verifyNotTree subshellAssignmentCheck "#!/bin/bash\nshopt -s lastpipe; echo a | read -r b; echo \"$b\"" +-- >>> prop $ verifyTree subshellAssignmentCheck "cat foo | while read bar; do a=$bar; done; echo \"$a\"" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "while read bar; do a=$bar; done < file; echo \"$a\"" +-- >>> prop $ verifyTree subshellAssignmentCheck "( A=foo; ); rm $A" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "( A=foo; rm $A; )" +-- >>> prop $ verifyTree subshellAssignmentCheck "cat foo | while read cow; do true; done; echo $cow;" +-- >>> prop $ verifyTree subshellAssignmentCheck "( export lol=$(ls); ); echo $lol;" +-- >>> prop $ verifyTree subshellAssignmentCheck "( typeset -a lol=a; ); echo $lol;" +-- >>> prop $ verifyTree subshellAssignmentCheck "cmd | while read foo; do (( n++ )); done; echo \"$n lines\"" +-- >>> prop $ verifyTree subshellAssignmentCheck "n=3 & echo $((n++))" +-- >>> prop $ verifyTree subshellAssignmentCheck "read n & n=foo$n" +-- >>> prop $ verifyTree subshellAssignmentCheck "(( n <<= 3 )) & (( n |= 4 )) &" +-- >>> prop $ verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let n=n+1; done\necho $n" +-- >>> prop $ verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let ++n; done\necho $n" +-- >>> prop $ verifyTree subshellAssignmentCheck "#!/bin/bash\necho foo | read bar; echo $bar" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "#!/bin/ksh93\necho foo | read bar; echo $bar" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "#!/bin/ksh\ncat foo | while read bar; do a=$bar; done\necho \"$a\"" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "(set -e); echo $@" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "foo=${ { bar=$(baz); } 2>&1; }; echo $foo $bar" +-- >>> prop $ verifyTree subshellAssignmentCheck "( exec {n}>&2; ); echo $n" +-- >>> prop $ verifyNotTree subshellAssignmentCheck "#!/bin/bash\nshopt -s lastpipe; echo a | read -r b; echo \"$b\"" subshellAssignmentCheck params t = let flow = variableFlow params check = findSubshelled flow [("oops",[])] Map.empty @@ -1613,41 +1614,41 @@ doVariableFlowAnalysis readFunc writeFunc empty flow = evalState ( -- | Check whether variables could have spaces/globs. -- --- prop> verifyTree checkSpacefulness "a='cow moo'; echo $a" --- prop> verifyNotTree checkSpacefulness "a='cow moo'; [[ $a ]]" --- prop> verifyNotTree checkSpacefulness "a='cow*.mp3'; echo \"$a\"" --- prop> verifyTree checkSpacefulness "for f in *.mp3; do echo $f; done" --- prop> verifyNotTree checkSpacefulness "foo=3; foo=$(echo $foo)" --- prop> verifyTree checkSpacefulness "a='*'; b=$a; c=lol${b//foo/bar}; echo $c" --- prop> verifyTree checkSpacefulness "a=foo$(lol); echo $a" --- prop> verifyTree checkSpacefulness "a=foo\\ bar; rm $a" --- prop> verifyNotTree checkSpacefulness "a=foo\\ bar; a=foo; rm $a" --- prop> verifyTree checkSpacefulness "rm $1" --- prop> verifyTree checkSpacefulness "rm ${10//foo/bar}" --- prop> verifyNotTree checkSpacefulness "(( $1 + 3 ))" --- prop> verifyNotTree checkSpacefulness "if [[ $2 -gt 14 ]]; then true; fi" --- prop> verifyNotTree checkSpacefulness "foo=$3 env" --- prop> verifyNotTree checkSpacefulness "local foo=$1" --- prop> verifyNotTree checkSpacefulness "declare foo=$1" --- prop> verifyTree checkSpacefulness "echo foo=$1" --- prop> verifyNotTree checkSpacefulness "$1 --flags" --- prop> verifyTree checkSpacefulness "echo $PWD" --- prop> verifyNotTree checkSpacefulness "n+='foo bar'" --- prop> verifyNotTree checkSpacefulness "select foo in $bar; do true; done" --- prop> verifyNotTree checkSpacefulness "echo $\"$1\"" --- prop> verifyNotTree checkSpacefulness "a=(1); echo ${a[@]}" --- prop> verifyTree checkSpacefulness "a='a b'; cat <<< $a" --- prop> verifyTree checkSpacefulness "a='s/[0-9]//g'; sed $a" --- prop> verifyTree checkSpacefulness "a='foo bar'; echo {1,2,$a}" --- prop> verifyNotTree checkSpacefulness "echo ${a:+'foo'}" --- prop> verifyNotTree checkSpacefulness "exec {n}>&1; echo $n" --- prop> verifyNotTree checkSpacefulness "n=$(stuff); exec {n}>&-;" --- prop> verifyTree checkSpacefulness "file='foo bar'; echo foo > $file;" --- prop> verifyNotTree checkSpacefulness "echo \"`echo \\\"$1\\\"`\"" --- prop> verifyNotTree checkSpacefulness "var=$1; [ -v var ]" --- prop> verifyTree checkSpacefulness "for file; do echo $file; done" --- prop> verifyTree checkSpacefulness "declare foo$n=$1" --- prop> verifyNotTree checkSpacefulness "echo ${1+\"$1\"}" +-- >>> prop $ verifyTree checkSpacefulness "a='cow moo'; echo $a" +-- >>> prop $ verifyNotTree checkSpacefulness "a='cow moo'; [[ $a ]]" +-- >>> prop $ verifyNotTree checkSpacefulness "a='cow*.mp3'; echo \"$a\"" +-- >>> prop $ verifyTree checkSpacefulness "for f in *.mp3; do echo $f; done" +-- >>> prop $ verifyNotTree checkSpacefulness "foo=3; foo=$(echo $foo)" +-- >>> prop $ verifyTree checkSpacefulness "a='*'; b=$a; c=lol${b//foo/bar}; echo $c" +-- >>> prop $ verifyTree checkSpacefulness "a=foo$(lol); echo $a" +-- >>> prop $ verifyTree checkSpacefulness "a=foo\\ bar; rm $a" +-- >>> prop $ verifyNotTree checkSpacefulness "a=foo\\ bar; a=foo; rm $a" +-- >>> prop $ verifyTree checkSpacefulness "rm $1" +-- >>> prop $ verifyTree checkSpacefulness "rm ${10//foo/bar}" +-- >>> prop $ verifyNotTree checkSpacefulness "(( $1 + 3 ))" +-- >>> prop $ verifyNotTree checkSpacefulness "if [[ $2 -gt 14 ]]; then true; fi" +-- >>> prop $ verifyNotTree checkSpacefulness "foo=$3 env" +-- >>> prop $ verifyNotTree checkSpacefulness "local foo=$1" +-- >>> prop $ verifyNotTree checkSpacefulness "declare foo=$1" +-- >>> prop $ verifyTree checkSpacefulness "echo foo=$1" +-- >>> prop $ verifyNotTree checkSpacefulness "$1 --flags" +-- >>> prop $ verifyTree checkSpacefulness "echo $PWD" +-- >>> prop $ verifyNotTree checkSpacefulness "n+='foo bar'" +-- >>> prop $ verifyNotTree checkSpacefulness "select foo in $bar; do true; done" +-- >>> prop $ verifyNotTree checkSpacefulness "echo $\"$1\"" +-- >>> prop $ verifyNotTree checkSpacefulness "a=(1); echo ${a[@]}" +-- >>> prop $ verifyTree checkSpacefulness "a='a b'; cat <<< $a" +-- >>> prop $ verifyTree checkSpacefulness "a='s/[0-9]//g'; sed $a" +-- >>> prop $ verifyTree checkSpacefulness "a='foo bar'; echo {1,2,$a}" +-- >>> prop $ verifyNotTree checkSpacefulness "echo ${a:+'foo'}" +-- >>> prop $ verifyNotTree checkSpacefulness "exec {n}>&1; echo $n" +-- >>> prop $ verifyNotTree checkSpacefulness "n=$(stuff); exec {n}>&-;" +-- >>> prop $ verifyTree checkSpacefulness "file='foo bar'; echo foo > $file;" +-- >>> prop $ verifyNotTree checkSpacefulness "echo \"`echo \\\"$1\\\"`\"" +-- >>> prop $ verifyNotTree checkSpacefulness "var=$1; [ -v var ]" +-- >>> prop $ verifyTree checkSpacefulness "for file; do echo $file; done" +-- >>> prop $ verifyTree checkSpacefulness "declare foo$n=$1" +-- >>> prop $ verifyNotTree checkSpacefulness "echo ${1+\"$1\"}" checkSpacefulness params t = doVariableFlowAnalysis readF writeF (Map.fromList defaults) (variableFlow params) @@ -1724,17 +1725,17 @@ checkSpacefulness params t = && isParamTo parents ":" token -- | --- prop> verifyTree checkQuotesInLiterals "param='--foo=\"bar\"'; app $param" --- prop> verifyTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; app $param" --- prop> verifyNotTree checkQuotesInLiterals "param='--foo=\"bar\"'; app \"$param\"" --- prop> verifyNotTree checkQuotesInLiterals "param=('--foo='); app \"${param[@]}\"" --- prop> verifyNotTree checkQuotesInLiterals "param=\"don't bother with this one\"; app $param" --- prop> verifyNotTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; eval app $param" --- prop> verifyTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm $param\"; $cmd" --- prop> verifyNotTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm ${#param}\"; $cmd" --- prop> verifyTree checkQuotesInLiterals "param='my\\ file'; rm $param" --- prop> verifyTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm $param" --- prop> verifyNotTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm ${#param}" +-- >>> prop $ verifyTree checkQuotesInLiterals "param='--foo=\"bar\"'; app $param" +-- >>> prop $ verifyTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; app $param" +-- >>> prop $ verifyNotTree checkQuotesInLiterals "param='--foo=\"bar\"'; app \"$param\"" +-- >>> prop $ verifyNotTree checkQuotesInLiterals "param=('--foo='); app \"${param[@]}\"" +-- >>> prop $ verifyNotTree checkQuotesInLiterals "param=\"don't bother with this one\"; app $param" +-- >>> prop $ verifyNotTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; eval app $param" +-- >>> prop $ verifyTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm $param\"; $cmd" +-- >>> prop $ verifyNotTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm ${#param}\"; $cmd" +-- >>> prop $ verifyTree checkQuotesInLiterals "param='my\\ file'; rm $param" +-- >>> prop $ verifyTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm $param" +-- >>> prop $ verifyNotTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm ${#param}" checkQuotesInLiterals params t = doVariableFlowAnalysis readF writeF Map.empty (variableFlow params) where @@ -1788,10 +1789,10 @@ checkQuotesInLiterals params t = -- | --- prop> verifyTree checkFunctionsUsedExternally "foo() { :; }; sudo foo" --- prop> verifyTree checkFunctionsUsedExternally "alias f='a'; xargs -n 1 f" --- prop> verifyNotTree checkFunctionsUsedExternally "f() { :; }; echo f" --- prop> verifyNotTree checkFunctionsUsedExternally "foo() { :; }; sudo \"foo\"" +-- >>> prop $ verifyTree checkFunctionsUsedExternally "foo() { :; }; sudo foo" +-- >>> prop $ verifyTree checkFunctionsUsedExternally "alias f='a'; xargs -n 1 f" +-- >>> prop $ verifyNotTree checkFunctionsUsedExternally "f() { :; }; echo f" +-- >>> prop $ verifyNotTree checkFunctionsUsedExternally "foo() { :; }; sudo \"foo\"" checkFunctionsUsedExternally params t = runNodeAnalysis checkCommand params t where @@ -1830,47 +1831,47 @@ checkFunctionsUsedExternally params t = "Use own script or sh -c '..' to run this from " ++ cmd ++ "." -- | --- prop> verifyNotTree checkUnusedAssignments "var=foo; echo $var" --- prop> verifyTree checkUnusedAssignments "var=foo; echo $bar" --- prop> verifyNotTree checkUnusedAssignments "var=foo; export var;" --- prop> verifyTree checkUnusedAssignments "for f in *; do echo '$f'; done" --- prop> verifyTree checkUnusedAssignments "local i=0" --- prop> verifyNotTree checkUnusedAssignments "read lol; echo $lol" --- prop> verifyNotTree checkUnusedAssignments "var=4; (( var++ ))" --- prop> verifyNotTree checkUnusedAssignments "var=2; $((var))" --- prop> verifyTree checkUnusedAssignments "var=2; var=3;" --- prop> verifyNotTree checkUnusedAssignments "read ''" --- prop> verifyNotTree checkUnusedAssignments "read -p 'test: '" --- prop> verifyNotTree checkUnusedAssignments "bar=5; export foo[$bar]=3" --- prop> verifyNotTree checkUnusedAssignments "read foo; echo ${!foo}" --- prop> verifyNotTree checkUnusedAssignments "x=(1); (( x[0] ))" --- prop> verifyNotTree checkUnusedAssignments "x=(1); n=0; echo ${x[n]}" --- prop> verifyNotTree checkUnusedAssignments "x=(1); n=0; (( x[n] ))" --- prop> verifyNotTree checkUnusedAssignments "foo=5; declare -x foo" --- prop> verifyNotTree checkUnusedAssignments "read -i 'foo' -e -p 'Input: ' bar; $bar;" --- prop> verifyNotTree checkUnusedAssignments "a=1; arr=( [$a]=42 ); echo \"${arr[@]}\"" --- prop> verifyNotTree checkUnusedAssignments "a=1; let b=a+1; echo $b" --- prop> verifyNotTree checkUnusedAssignments "a=1; PS1='$a'" --- prop> verifyNotTree checkUnusedAssignments "a=1; trap 'echo $a' INT" --- prop> verifyNotTree checkUnusedAssignments "a=1; [ -v a ]" --- prop> verifyNotTree checkUnusedAssignments "a=1; [ -R a ]" --- prop> verifyNotTree checkUnusedAssignments "mapfile -C a b; echo ${b[@]}" --- prop> verifyNotTree checkUnusedAssignments "readarray foo; echo ${foo[@]}" --- prop> verifyNotTree checkUnusedAssignments "declare -F foo" --- prop> verifyTree checkUnusedAssignments "var=3; [ var -eq 3 ]" --- prop> verifyNotTree checkUnusedAssignments "var=3; [[ var -eq 3 ]]" --- prop> verifyNotTree checkUnusedAssignments "var=(a b); declare -p var" --- prop> verifyTree checkUnusedAssignments "let a=1" --- prop> verifyTree checkUnusedAssignments "let 'a=1'" --- prop> verifyTree checkUnusedAssignments "let a=b=c; echo $a" --- prop> verifyNotTree checkUnusedAssignments "a=foo; [[ foo =~ ^{$a}$ ]]" --- prop> verifyNotTree checkUnusedAssignments "foo=1; (( t = foo )); echo $t" --- prop> verifyNotTree checkUnusedAssignments "a=foo; b=2; echo ${a:b}" --- prop> verifyNotTree checkUnusedAssignments "if [[ -v foo ]]; then true; fi" --- prop> verifyNotTree checkUnusedAssignments "fd=2; exec {fd}>&-" --- prop> verifyTree checkUnusedAssignments "(( a=42 ))" --- prop> verifyNotTree checkUnusedAssignments "declare -x -f foo" --- prop> verifyNotTree checkUnusedAssignments "arr=(1 2); num=2; echo \"${arr[@]:num}\"" +-- >>> prop $ verifyNotTree checkUnusedAssignments "var=foo; echo $var" +-- >>> prop $ verifyTree checkUnusedAssignments "var=foo; echo $bar" +-- >>> prop $ verifyNotTree checkUnusedAssignments "var=foo; export var;" +-- >>> prop $ verifyTree checkUnusedAssignments "for f in *; do echo '$f'; done" +-- >>> prop $ verifyTree checkUnusedAssignments "local i=0" +-- >>> prop $ verifyNotTree checkUnusedAssignments "read lol; echo $lol" +-- >>> prop $ verifyNotTree checkUnusedAssignments "var=4; (( var++ ))" +-- >>> prop $ verifyNotTree checkUnusedAssignments "var=2; $((var))" +-- >>> prop $ verifyTree checkUnusedAssignments "var=2; var=3;" +-- >>> prop $ verifyNotTree checkUnusedAssignments "read ''" +-- >>> prop $ verifyNotTree checkUnusedAssignments "read -p 'test: '" +-- >>> prop $ verifyNotTree checkUnusedAssignments "bar=5; export foo[$bar]=3" +-- >>> prop $ verifyNotTree checkUnusedAssignments "read foo; echo ${!foo}" +-- >>> prop $ verifyNotTree checkUnusedAssignments "x=(1); (( x[0] ))" +-- >>> prop $ verifyNotTree checkUnusedAssignments "x=(1); n=0; echo ${x[n]}" +-- >>> prop $ verifyNotTree checkUnusedAssignments "x=(1); n=0; (( x[n] ))" +-- >>> prop $ verifyNotTree checkUnusedAssignments "foo=5; declare -x foo" +-- >>> prop $ verifyNotTree checkUnusedAssignments "read -i 'foo' -e -p 'Input: ' bar; $bar;" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=1; arr=( [$a]=42 ); echo \"${arr[@]}\"" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=1; let b=a+1; echo $b" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=1; PS1='$a'" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=1; trap 'echo $a' INT" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=1; [ -v a ]" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=1; [ -R a ]" +-- >>> prop $ verifyNotTree checkUnusedAssignments "mapfile -C a b; echo ${b[@]}" +-- >>> prop $ verifyNotTree checkUnusedAssignments "readarray foo; echo ${foo[@]}" +-- >>> prop $ verifyNotTree checkUnusedAssignments "declare -F foo" +-- >>> prop $ verifyTree checkUnusedAssignments "var=3; [ var -eq 3 ]" +-- >>> prop $ verifyNotTree checkUnusedAssignments "var=3; [[ var -eq 3 ]]" +-- >>> prop $ verifyNotTree checkUnusedAssignments "var=(a b); declare -p var" +-- >>> prop $ verifyTree checkUnusedAssignments "let a=1" +-- >>> prop $ verifyTree checkUnusedAssignments "let 'a=1'" +-- >>> prop $ verifyTree checkUnusedAssignments "let a=b=c; echo $a" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=foo; [[ foo =~ ^{$a}$ ]]" +-- >>> prop $ verifyNotTree checkUnusedAssignments "foo=1; (( t = foo )); echo $t" +-- >>> prop $ verifyNotTree checkUnusedAssignments "a=foo; b=2; echo ${a:b}" +-- >>> prop $ verifyNotTree checkUnusedAssignments "if [[ -v foo ]]; then true; fi" +-- >>> prop $ verifyNotTree checkUnusedAssignments "fd=2; exec {fd}>&-" +-- >>> prop $ verifyTree checkUnusedAssignments "(( a=42 ))" +-- >>> prop $ verifyNotTree checkUnusedAssignments "declare -x -f foo" +-- >>> prop $ verifyNotTree checkUnusedAssignments "arr=(1 2); num=2; echo \"${arr[@]:num}\"" checkUnusedAssignments params t = execWriter (mapM_ warnFor unused) where flow = variableFlow params @@ -1894,41 +1895,41 @@ checkUnusedAssignments params t = execWriter (mapM_ warnFor unused) defaultMap = Map.fromList $ zip internalVariables $ repeat () -- | --- prop> verifyTree checkUnassignedReferences "echo $foo" --- prop> verifyNotTree checkUnassignedReferences "foo=hello; echo $foo" --- prop> verifyTree checkUnassignedReferences "MY_VALUE=3; echo $MYVALUE" --- prop> verifyNotTree checkUnassignedReferences "RANDOM2=foo; echo $RANDOM" --- prop> verifyNotTree checkUnassignedReferences "declare -A foo=([bar]=baz); echo ${foo[bar]}" --- prop> verifyNotTree checkUnassignedReferences "foo=..; echo ${foo-bar}" --- prop> verifyNotTree checkUnassignedReferences "getopts ':h' foo; echo $foo" --- prop> verifyNotTree checkUnassignedReferences "let 'foo = 1'; echo $foo" --- prop> verifyNotTree checkUnassignedReferences "echo ${foo-bar}" --- prop> verifyNotTree checkUnassignedReferences "echo ${foo:?}" --- prop> verifyNotTree checkUnassignedReferences "declare -A foo; echo \"${foo[@]}\"" --- prop> verifyNotTree checkUnassignedReferences "typeset -a foo; echo \"${foo[@]}\"" --- prop> verifyNotTree checkUnassignedReferences "f() { local foo; echo $foo; }" --- prop> verifyNotTree checkUnassignedReferences "foo=; echo $foo" --- prop> verifyNotTree checkUnassignedReferences "f() { true; }; export -f f" --- prop> verifyNotTree checkUnassignedReferences "declare -A foo=( [a b]=bar ); echo ${foo[a b]}" --- prop> verifyNotTree checkUnassignedReferences "USERS=foo; echo $USER" --- prop> verifyNotTree checkUnassignedReferences "FOOBAR=42; export FOOBAR=" --- prop> verifyNotTree checkUnassignedReferences "readonly foo=bar; echo $foo" --- prop> verifyNotTree checkUnassignedReferences "printf -v foo bar; echo $foo" --- prop> verifyTree checkUnassignedReferences "echo ${#foo}" --- prop> verifyNotTree checkUnassignedReferences "echo ${!os*}" --- prop> verifyTree checkUnassignedReferences "declare -a foo; foo[bar]=42;" --- prop> verifyNotTree checkUnassignedReferences "declare -A foo; foo[bar]=42;" --- prop> verifyNotTree checkUnassignedReferences "declare -A foo=(); foo[bar]=42;" --- prop> verifyNotTree checkUnassignedReferences "a::b() { foo; }; readonly -f a::b" --- prop> verifyNotTree checkUnassignedReferences ": ${foo:=bar}" --- prop> verifyNotTree checkUnassignedReferences "#!/bin/ksh\necho \"${.sh.version}\"\n" --- prop> verifyNotTree checkUnassignedReferences "if [[ -v foo ]]; then echo $foo; fi" --- prop> verifyNotTree checkUnassignedReferences "if [[ -v foo[3] ]]; then echo ${foo[3]}; fi" --- prop> verifyNotTree checkUnassignedReferences "X=1; if [[ -v foo[$X+42] ]]; then echo ${foo[$X+42]}; fi" --- prop> verifyNotTree checkUnassignedReferences "if [[ -v \"foo[1]\" ]]; then echo ${foo[@]}; fi" --- prop> verifyNotTree checkUnassignedReferences "f() { local -A foo; echo \"${foo[@]}\"; }" --- prop> verifyNotTree checkUnassignedReferences "declare -A foo; (( foo[bar] ))" --- prop> verifyNotTree checkUnassignedReferences "echo ${arr[foo-bar]:?fail}" +-- >>> prop $ verifyTree checkUnassignedReferences "echo $foo" +-- >>> prop $ verifyNotTree checkUnassignedReferences "foo=hello; echo $foo" +-- >>> prop $ verifyTree checkUnassignedReferences "MY_VALUE=3; echo $MYVALUE" +-- >>> prop $ verifyNotTree checkUnassignedReferences "RANDOM2=foo; echo $RANDOM" +-- >>> prop $ verifyNotTree checkUnassignedReferences "declare -A foo=([bar]=baz); echo ${foo[bar]}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "foo=..; echo ${foo-bar}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "getopts ':h' foo; echo $foo" +-- >>> prop $ verifyNotTree checkUnassignedReferences "let 'foo = 1'; echo $foo" +-- >>> prop $ verifyNotTree checkUnassignedReferences "echo ${foo-bar}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "echo ${foo:?}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "declare -A foo; echo \"${foo[@]}\"" +-- >>> prop $ verifyNotTree checkUnassignedReferences "typeset -a foo; echo \"${foo[@]}\"" +-- >>> prop $ verifyNotTree checkUnassignedReferences "f() { local foo; echo $foo; }" +-- >>> prop $ verifyNotTree checkUnassignedReferences "foo=; echo $foo" +-- >>> prop $ verifyNotTree checkUnassignedReferences "f() { true; }; export -f f" +-- >>> prop $ verifyNotTree checkUnassignedReferences "declare -A foo=( [a b]=bar ); echo ${foo[a b]}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "USERS=foo; echo $USER" +-- >>> prop $ verifyNotTree checkUnassignedReferences "FOOBAR=42; export FOOBAR=" +-- >>> prop $ verifyNotTree checkUnassignedReferences "readonly foo=bar; echo $foo" +-- >>> prop $ verifyNotTree checkUnassignedReferences "printf -v foo bar; echo $foo" +-- >>> prop $ verifyTree checkUnassignedReferences "echo ${#foo}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "echo ${!os*}" +-- >>> prop $ verifyTree checkUnassignedReferences "declare -a foo; foo[bar]=42;" +-- >>> prop $ verifyNotTree checkUnassignedReferences "declare -A foo; foo[bar]=42;" +-- >>> prop $ verifyNotTree checkUnassignedReferences "declare -A foo=(); foo[bar]=42;" +-- >>> prop $ verifyNotTree checkUnassignedReferences "a::b() { foo; }; readonly -f a::b" +-- >>> prop $ verifyNotTree checkUnassignedReferences ": ${foo:=bar}" +-- >>> prop $ verifyNotTree checkUnassignedReferences "#!/bin/ksh\necho \"${.sh.version}\"\n" +-- >>> prop $ verifyNotTree checkUnassignedReferences "if [[ -v foo ]]; then echo $foo; fi" +-- >>> prop $ verifyNotTree checkUnassignedReferences "if [[ -v foo[3] ]]; then echo ${foo[3]}; fi" +-- >>> prop $ verifyNotTree checkUnassignedReferences "X=1; if [[ -v foo[$X+42] ]]; then echo ${foo[$X+42]}; fi" +-- >>> prop $ verifyNotTree checkUnassignedReferences "if [[ -v \"foo[1]\" ]]; then echo ${foo[@]}; fi" +-- >>> prop $ verifyNotTree checkUnassignedReferences "f() { local -A foo; echo \"${foo[@]}\"; }" +-- >>> prop $ verifyNotTree checkUnassignedReferences "declare -A foo; (( foo[bar] ))" +-- >>> prop $ verifyNotTree checkUnassignedReferences "echo ${arr[foo-bar]:?fail}" checkUnassignedReferences params t = warnings where (readMap, writeMap) = execState (mapM tally $ variableFlow params) (Map.empty, Map.empty) @@ -2003,10 +2004,10 @@ checkUnassignedReferences params t = warnings -- | --- prop> verify checkGlobsAsOptions "rm *.txt" --- prop> verify checkGlobsAsOptions "ls ??.*" --- prop> verifyNot checkGlobsAsOptions "rm -- *.txt" --- prop> verifyNot checkGlobsAsOptions "*.txt" +-- >>> prop $ verify checkGlobsAsOptions "rm *.txt" +-- >>> prop $ verify checkGlobsAsOptions "ls ??.*" +-- >>> prop $ verifyNot checkGlobsAsOptions "rm -- *.txt" +-- >>> prop $ verifyNot checkGlobsAsOptions "*.txt" checkGlobsAsOptions _ (T_SimpleCommand _ _ args) = mapM_ check $ takeWhile (not . isEndOfArgs) (drop 1 args) where @@ -2025,14 +2026,14 @@ checkGlobsAsOptions _ _ = return () -- | --- prop> verify checkWhileReadPitfalls "while read foo; do ssh $foo uptime; done < file" --- prop> verifyNot checkWhileReadPitfalls "while read -u 3 foo; do ssh $foo uptime; done 3< file" --- prop> verifyNot checkWhileReadPitfalls "while true; do ssh host uptime; done" --- prop> verifyNot checkWhileReadPitfalls "while read foo; do ssh $foo hostname < /dev/null; done" --- prop> verifyNot checkWhileReadPitfalls "while read foo; do echo ls | ssh $foo; done" --- prop> verifyNot checkWhileReadPitfalls "while read foo <&3; do ssh $foo; done 3< foo" --- prop> verify checkWhileReadPitfalls "while read foo; do if true; then ssh $foo uptime; fi; done < file" --- prop> verifyNot checkWhileReadPitfalls "while read foo; do ssh -n $foo uptime; done < file" +-- >>> prop $ verify checkWhileReadPitfalls "while read foo; do ssh $foo uptime; done < file" +-- >>> prop $ verifyNot checkWhileReadPitfalls "while read -u 3 foo; do ssh $foo uptime; done 3< file" +-- >>> prop $ verifyNot checkWhileReadPitfalls "while true; do ssh host uptime; done" +-- >>> prop $ verifyNot checkWhileReadPitfalls "while read foo; do ssh $foo hostname < /dev/null; done" +-- >>> prop $ verifyNot checkWhileReadPitfalls "while read foo; do echo ls | ssh $foo; done" +-- >>> prop $ verifyNot checkWhileReadPitfalls "while read foo <&3; do ssh $foo; done 3< foo" +-- >>> prop $ verify checkWhileReadPitfalls "while read foo; do if true; then ssh $foo uptime; fi; done < file" +-- >>> prop $ verifyNot checkWhileReadPitfalls "while read foo; do ssh -n $foo uptime; done < file" checkWhileReadPitfalls _ (T_WhileExpression id [command] contents) | isStdinReadCommand command = @@ -2074,8 +2075,8 @@ checkWhileReadPitfalls _ _ = return () -- | --- prop> verify checkPrefixAssignmentReference "var=foo echo $var" --- prop> verifyNot checkPrefixAssignmentReference "var=$(echo $var) cmd" +-- >>> prop $ verify checkPrefixAssignmentReference "var=foo echo $var" +-- >>> prop $ verifyNot checkPrefixAssignmentReference "var=$(echo $var) cmd" checkPrefixAssignmentReference params t@(T_DollarBraced id value) = check path where @@ -2097,11 +2098,11 @@ checkPrefixAssignmentReference params t@(T_DollarBraced id value) = checkPrefixAssignmentReference _ _ = return () -- | --- prop> verify checkCharRangeGlob "ls *[:digit:].jpg" --- prop> verifyNot checkCharRangeGlob "ls *[[:digit:]].jpg" --- prop> verify checkCharRangeGlob "ls [10-15]" --- prop> verifyNot checkCharRangeGlob "ls [a-zA-Z]" --- prop> verifyNot checkCharRangeGlob "tr -d [a-zA-Z]" -- tr has 2060 +-- >>> prop $ verify checkCharRangeGlob "ls *[:digit:].jpg" +-- >>> prop $ verifyNot checkCharRangeGlob "ls *[[:digit:]].jpg" +-- >>> prop $ verify checkCharRangeGlob "ls [10-15]" +-- >>> prop $ verifyNot checkCharRangeGlob "ls [a-zA-Z]" +-- >>> prop $ verifyNot checkCharRangeGlob "tr -d [a-zA-Z]" -- tr has 2060 checkCharRangeGlob p t@(T_Glob id str) | isCharClass str && not (isParamTo (parentMap p) "tr" t) = if ":" `isPrefixOf` contents @@ -2120,10 +2121,10 @@ checkCharRangeGlob _ _ = return () -- | --- prop> verify checkCdAndBack "for f in *; do cd $f; git pull; cd ..; done" --- prop> verifyNot checkCdAndBack "for f in *; do cd $f || continue; git pull; cd ..; done" --- prop> verifyNot checkCdAndBack "while [[ $PWD != / ]]; do cd ..; done" --- prop> verify checkCdAndBack "cd $tmp; foo; cd -" +-- >>> prop $ verify checkCdAndBack "for f in *; do cd $f; git pull; cd ..; done" +-- >>> prop $ verifyNot checkCdAndBack "for f in *; do cd $f || continue; git pull; cd ..; done" +-- >>> prop $ verifyNot checkCdAndBack "while [[ $PWD != / ]]; do cd ..; done" +-- >>> prop $ verify checkCdAndBack "cd $tmp; foo; cd -" checkCdAndBack params = doLists where shell = shellType params @@ -2154,13 +2155,13 @@ checkCdAndBack params = doLists message = "Use a ( subshell ) to avoid having to cd back." -- | --- prop> verify checkLoopKeywordScope "continue 2" --- prop> verify checkLoopKeywordScope "for f; do ( break; ); done" --- prop> verify checkLoopKeywordScope "if true; then continue; fi" --- prop> verifyNot checkLoopKeywordScope "while true; do break; done" --- prop> verify checkLoopKeywordScope "if true; then break; fi" --- prop> verify checkLoopKeywordScope "while true; do true | { break; }; done" --- prop> verifyNot checkLoopKeywordScope "#!/bin/ksh\nwhile true; do true | { break; }; done" +-- >>> prop $ verify checkLoopKeywordScope "continue 2" +-- >>> prop $ verify checkLoopKeywordScope "for f; do ( break; ); done" +-- >>> prop $ verify checkLoopKeywordScope "if true; then continue; fi" +-- >>> prop $ verifyNot checkLoopKeywordScope "while true; do break; done" +-- >>> prop $ verify checkLoopKeywordScope "if true; then break; fi" +-- >>> prop $ verify checkLoopKeywordScope "while true; do true | { break; }; done" +-- >>> prop $ verifyNot checkLoopKeywordScope "#!/bin/ksh\nwhile true; do true | { break; }; done" checkLoopKeywordScope params t | name `elem` map Just ["continue", "break"] = if not $ any isLoop path @@ -2183,9 +2184,9 @@ checkLoopKeywordScope _ _ = return () -- | --- prop> verify checkFunctionDeclarations "#!/bin/ksh\nfunction foo() { command foo --lol \"$@\"; }" --- prop> verify checkFunctionDeclarations "#!/bin/dash\nfunction foo { lol; }" --- prop> verifyNot checkFunctionDeclarations "foo() { echo bar; }" +-- >>> prop $ verify checkFunctionDeclarations "#!/bin/ksh\nfunction foo() { command foo --lol \"$@\"; }" +-- >>> prop $ verify checkFunctionDeclarations "#!/bin/dash\nfunction foo { lol; }" +-- >>> prop $ verifyNot checkFunctionDeclarations "foo() { echo bar; }" checkFunctionDeclarations params (T_Function id (FunctionKeyword hasKeyword) (FunctionParentheses hasParens) _ _) = case shellType params of @@ -2207,8 +2208,8 @@ checkFunctionDeclarations _ _ = return () -- | --- prop> verify checkStderrPipe "#!/bin/ksh\nfoo |& bar" --- prop> verifyNot checkStderrPipe "#!/bin/bash\nfoo |& bar" +-- >>> prop $ verify checkStderrPipe "#!/bin/ksh\nfoo |& bar" +-- >>> prop $ verifyNot checkStderrPipe "#!/bin/bash\nfoo |& bar" checkStderrPipe params = case shellType params of Ksh -> match @@ -2219,18 +2220,18 @@ checkStderrPipe params = match _ = return () -- | --- prop> verifyTree checkUnpassedInFunctions "foo() { echo $1; }; foo" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $1; };" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $lol; }; foo" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $0; }; foo" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $1; }; foo 'lol'; foo" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { set -- *; echo $1; }; foo" --- prop> verifyTree checkUnpassedInFunctions "foo() { echo $1; }; foo; foo;" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $((1)); }; foo;" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $(($b)); }; foo;" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo $!; }; foo;" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { bar() { echo $1; }; bar baz; }; foo;" --- prop> verifyNotTree checkUnpassedInFunctions "foo() { echo ${!var*}; }; foo;" +-- >>> prop $ verifyTree checkUnpassedInFunctions "foo() { echo $1; }; foo" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $1; };" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $lol; }; foo" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $0; }; foo" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $1; }; foo 'lol'; foo" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { set -- *; echo $1; }; foo" +-- >>> prop $ verifyTree checkUnpassedInFunctions "foo() { echo $1; }; foo; foo;" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $((1)); }; foo;" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $(($b)); }; foo;" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo $!; }; foo;" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { bar() { echo $1; }; bar baz; }; foo;" +-- >>> prop $ verifyNotTree checkUnpassedInFunctions "foo() { echo ${!var*}; }; foo;" checkUnpassedInFunctions params root = execWriter $ mapM_ warnForGroup referenceGroups where @@ -2295,14 +2296,14 @@ checkUnpassedInFunctions params root = -- | --- prop> verify checkOverridingPath "PATH=\"$var/$foo\"" --- prop> verify checkOverridingPath "PATH=\"mydir\"" --- prop> verify checkOverridingPath "PATH=/cow/foo" --- prop> verifyNot checkOverridingPath "PATH=/cow/foo/bin" --- prop> verifyNot checkOverridingPath "PATH='/bin:/sbin'" --- prop> verifyNot checkOverridingPath "PATH=\"$var/$foo\" cmd" --- prop> verifyNot checkOverridingPath "PATH=$OLDPATH" --- prop> verifyNot checkOverridingPath "PATH=$PATH:/stuff" +-- >>> prop $ verify checkOverridingPath "PATH=\"$var/$foo\"" +-- >>> prop $ verify checkOverridingPath "PATH=\"mydir\"" +-- >>> prop $ verify checkOverridingPath "PATH=/cow/foo" +-- >>> prop $ verifyNot checkOverridingPath "PATH=/cow/foo/bin" +-- >>> prop $ verifyNot checkOverridingPath "PATH='/bin:/sbin'" +-- >>> prop $ verifyNot checkOverridingPath "PATH=\"$var/$foo\" cmd" +-- >>> prop $ verifyNot checkOverridingPath "PATH=$OLDPATH" +-- >>> prop $ verifyNot checkOverridingPath "PATH=$PATH:/stuff" checkOverridingPath _ (T_SimpleCommand _ vars []) = mapM_ checkVar vars where @@ -2316,9 +2317,9 @@ checkOverridingPath _ (T_SimpleCommand _ vars []) = checkOverridingPath _ _ = return () -- | --- prop> verify checkTildeInPath "PATH=\"$PATH:~/bin\"" --- prop> verify checkTildeInPath "PATH='~foo/bin'" --- prop> verifyNot checkTildeInPath "PATH=~/bin" +-- >>> prop $ verify checkTildeInPath "PATH=\"$PATH:~/bin\"" +-- >>> prop $ verify checkTildeInPath "PATH='~foo/bin'" +-- >>> prop $ verifyNot checkTildeInPath "PATH=~/bin" checkTildeInPath _ (T_SimpleCommand _ vars _) = mapM_ checkVar vars where @@ -2334,9 +2335,9 @@ checkTildeInPath _ (T_SimpleCommand _ vars _) = checkTildeInPath _ _ = return () -- | --- prop> verify checkUnsupported "#!/bin/sh\ncase foo in bar) baz ;& esac" --- prop> verify checkUnsupported "#!/bin/ksh\ncase foo in bar) baz ;;& esac" --- prop> verify checkUnsupported "#!/bin/bash\necho \"${ ls; }\"" +-- >>> prop $ verify checkUnsupported "#!/bin/sh\ncase foo in bar) baz ;& esac" +-- >>> prop $ verify checkUnsupported "#!/bin/ksh\ncase foo in bar) baz ;;& esac" +-- >>> prop $ verify checkUnsupported "#!/bin/bash\necho \"${ ls; }\"" checkUnsupported params t = when (not (null support) && (shellType params `notElem` support)) $ report name @@ -2361,9 +2362,9 @@ shellSupport t = groupWith f = groupBy ((==) `on` f) -- | --- prop> verify checkMultipleAppends "foo >> file; bar >> file; baz >> file;" --- prop> verify checkMultipleAppends "foo >> file; bar | grep f >> file; baz >> file;" --- prop> verifyNot checkMultipleAppends "foo < file; bar < file; baz < file;" +-- >>> prop $ verify checkMultipleAppends "foo >> file; bar >> file; baz >> file;" +-- >>> prop $ verify checkMultipleAppends "foo >> file; bar | grep f >> file; baz >> file;" +-- >>> prop $ verifyNot checkMultipleAppends "foo < file; bar < file; baz < file;" checkMultipleAppends params t = mapM_ checkList $ getCommandSequences t where @@ -2384,8 +2385,8 @@ checkMultipleAppends params t = -- | --- prop> verify checkSuspiciousIFS "IFS=\"\\n\"" --- prop> verifyNot checkSuspiciousIFS "IFS=$'\\t'" +-- >>> prop $ verify checkSuspiciousIFS "IFS=\"\\n\"" +-- >>> prop $ verifyNot checkSuspiciousIFS "IFS=$'\\t'" checkSuspiciousIFS params (T_Assignment id Assign "IFS" [] value) = potentially $ do str <- getLiteralString value @@ -2405,12 +2406,12 @@ checkSuspiciousIFS _ _ = return () -- | --- prop> verify checkShouldUseGrepQ "[[ $(foo | grep bar) ]]" --- prop> verify checkShouldUseGrepQ "[ -z $(fgrep lol) ]" --- prop> verify checkShouldUseGrepQ "[ -n \"$(foo | zgrep lol)\" ]" --- prop> verifyNot checkShouldUseGrepQ "[ -z $(grep bar | cmd) ]" --- prop> verifyNot checkShouldUseGrepQ "rm $(ls | grep file)" --- prop> verifyNot checkShouldUseGrepQ "[[ -n $(pgrep foo) ]]" +-- >>> prop $ verify checkShouldUseGrepQ "[[ $(foo | grep bar) ]]" +-- >>> prop $ verify checkShouldUseGrepQ "[ -z $(fgrep lol) ]" +-- >>> prop $ verify checkShouldUseGrepQ "[ -n \"$(foo | zgrep lol)\" ]" +-- >>> prop $ verifyNot checkShouldUseGrepQ "[ -z $(grep bar | cmd) ]" +-- >>> prop $ verifyNot checkShouldUseGrepQ "rm $(ls | grep file)" +-- >>> prop $ verifyNot checkShouldUseGrepQ "[[ -n $(pgrep foo) ]]" checkShouldUseGrepQ params t = potentially $ case t of TC_Nullary id _ token -> check id True token @@ -2442,22 +2443,22 @@ checkShouldUseGrepQ params t = isGrep = (`elem` ["grep", "egrep", "fgrep", "zgrep"]) -- | --- prop> verify checkTestArgumentSplitting "[ -e *.mp3 ]" --- prop> verifyNot checkTestArgumentSplitting "[[ $a == *b* ]]" --- prop> verify checkTestArgumentSplitting "[[ *.png == '' ]]" --- prop> verify checkTestArgumentSplitting "[[ foo == f{o,oo,ooo} ]]" --- prop> verify checkTestArgumentSplitting "[[ $@ ]]" --- prop> verify checkTestArgumentSplitting "[ -e $@ ]" --- prop> verify checkTestArgumentSplitting "[ $@ == $@ ]" --- prop> verify checkTestArgumentSplitting "[[ $@ = $@ ]]" --- prop> verifyNot checkTestArgumentSplitting "[[ foo =~ bar{1,2} ]]" --- prop> verifyNot checkTestArgumentSplitting "[ \"$@\" ]" --- prop> verify checkTestArgumentSplitting "[[ \"$@\" ]]" --- prop> verify checkTestArgumentSplitting "[ *.png ]" --- prop> verify checkTestArgumentSplitting "[ \"$@\" == \"\" ]" --- prop> verify checkTestArgumentSplitting "[[ \"$@\" == \"\" ]]" --- prop> verifyNot checkTestArgumentSplitting "[[ \"$*\" == \"\" ]]" --- prop> verifyNot checkTestArgumentSplitting "[[ -v foo[123] ]]" +-- >>> prop $ verify checkTestArgumentSplitting "[ -e *.mp3 ]" +-- >>> prop $ verifyNot checkTestArgumentSplitting "[[ $a == *b* ]]" +-- >>> prop $ verify checkTestArgumentSplitting "[[ *.png == '' ]]" +-- >>> prop $ verify checkTestArgumentSplitting "[[ foo == f{o,oo,ooo} ]]" +-- >>> prop $ verify checkTestArgumentSplitting "[[ $@ ]]" +-- >>> prop $ verify checkTestArgumentSplitting "[ -e $@ ]" +-- >>> prop $ verify checkTestArgumentSplitting "[ $@ == $@ ]" +-- >>> prop $ verify checkTestArgumentSplitting "[[ $@ = $@ ]]" +-- >>> prop $ verifyNot checkTestArgumentSplitting "[[ foo =~ bar{1,2} ]]" +-- >>> prop $ verifyNot checkTestArgumentSplitting "[ \"$@\" ]" +-- >>> prop $ verify checkTestArgumentSplitting "[[ \"$@\" ]]" +-- >>> prop $ verify checkTestArgumentSplitting "[ *.png ]" +-- >>> prop $ verify checkTestArgumentSplitting "[ \"$@\" == \"\" ]" +-- >>> prop $ verify checkTestArgumentSplitting "[[ \"$@\" == \"\" ]]" +-- >>> prop $ verifyNot checkTestArgumentSplitting "[[ \"$*\" == \"\" ]]" +-- >>> prop $ verifyNot checkTestArgumentSplitting "[[ -v foo[123] ]]" checkTestArgumentSplitting :: Parameters -> Token -> Writer [TokenComment] () checkTestArgumentSplitting _ t = case t of @@ -2513,11 +2514,11 @@ checkTestArgumentSplitting _ t = -- | --- prop> verify checkMaskedReturns "f() { local a=$(false); }" --- prop> verify checkMaskedReturns "declare a=$(false)" --- prop> verify checkMaskedReturns "declare a=\"`false`\"" --- prop> verifyNot checkMaskedReturns "declare a; a=$(false)" --- prop> verifyNot checkMaskedReturns "f() { local -r a=$(false); }" +-- >>> prop $ verify checkMaskedReturns "f() { local a=$(false); }" +-- >>> prop $ verify checkMaskedReturns "declare a=$(false)" +-- >>> prop $ verify checkMaskedReturns "declare a=\"`false`\"" +-- >>> prop $ verifyNot checkMaskedReturns "declare a; a=$(false)" +-- >>> prop $ verifyNot checkMaskedReturns "f() { local -r a=$(false); }" checkMaskedReturns _ t@(T_SimpleCommand id _ (cmd:rest)) = potentially $ do name <- getCommandName t guard $ name `elem` ["declare", "export"] @@ -2536,41 +2537,41 @@ checkMaskedReturns _ _ = return () -- | --- prop> verify checkReadWithoutR "read -a foo" --- prop> verifyNot checkReadWithoutR "read -ar foo" +-- >>> prop $ verify checkReadWithoutR "read -a foo" +-- >>> prop $ verifyNot checkReadWithoutR "read -ar foo" checkReadWithoutR _ t@T_SimpleCommand {} | t `isUnqualifiedCommand` "read" = unless ("r" `elem` map snd (getAllFlags t)) $ info (getId $ getCommandTokenOrThis t) 2162 "read without -r will mangle backslashes." checkReadWithoutR _ _ = return () -- | --- prop> verifyTree checkUncheckedCdPushdPopd "cd ~/src; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "cd ~/src || exit; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "set -e; cd ~/src; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "if cd foo; then rm foo; fi" --- prop> verifyTree checkUncheckedCdPushdPopd "if true; then cd foo; fi" --- prop> verifyNotTree checkUncheckedCdPushdPopd "cd .." --- prop> verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\ncd foo\nrm bar" --- prop> verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; cd foo; rm bar" --- prop> verifyTree checkUncheckedCdPushdPopd "builtin cd ~/src; rm -r foo" --- prop> verifyTree checkUncheckedCdPushdPopd "pushd ~/src; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "pushd ~/src || exit; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "set -e; pushd ~/src; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "if pushd foo; then rm foo; fi" --- prop> verifyTree checkUncheckedCdPushdPopd "if true; then pushd foo; fi" --- prop> verifyNotTree checkUncheckedCdPushdPopd "pushd .." --- prop> verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\npushd foo\nrm bar" --- prop> verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; pushd foo; rm bar" --- prop> verifyNotTree checkUncheckedCdPushdPopd "pushd -n foo" --- prop> verifyTree checkUncheckedCdPushdPopd "popd; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "popd || exit; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "set -e; popd; rm -r foo" --- prop> verifyNotTree checkUncheckedCdPushdPopd "if popd; then rm foo; fi" --- prop> verifyTree checkUncheckedCdPushdPopd "if true; then popd; fi" --- prop> verifyTree checkUncheckedCdPushdPopd "popd" --- prop> verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\npopd\nrm bar" --- prop> verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; popd; rm bar" --- prop> verifyNotTree checkUncheckedCdPushdPopd "popd -n foo" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "cd ~/src; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "cd ~/src || exit; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "set -e; cd ~/src; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "if cd foo; then rm foo; fi" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "if true; then cd foo; fi" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "cd .." +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\ncd foo\nrm bar" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; cd foo; rm bar" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "builtin cd ~/src; rm -r foo" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "pushd ~/src; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "pushd ~/src || exit; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "set -e; pushd ~/src; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "if pushd foo; then rm foo; fi" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "if true; then pushd foo; fi" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "pushd .." +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\npushd foo\nrm bar" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; pushd foo; rm bar" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "pushd -n foo" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "popd; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "popd || exit; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "set -e; popd; rm -r foo" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "if popd; then rm foo; fi" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "if true; then popd; fi" +-- >>> prop $ verifyTree checkUncheckedCdPushdPopd "popd" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\npopd\nrm bar" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; popd; rm bar" +-- >>> prop $ verifyNotTree checkUncheckedCdPushdPopd "popd -n foo" checkUncheckedCdPushdPopd params root = if hasSetE params then @@ -2590,9 +2591,9 @@ checkUncheckedCdPushdPopd params root = _ -> False -- | --- prop> verify checkLoopVariableReassignment "for i in *; do for i in *.bar; do true; done; done" --- prop> verify checkLoopVariableReassignment "for i in *; do for((i=0; i<3; i++)); do true; done; done" --- prop> verifyNot checkLoopVariableReassignment "for i in *; do for j in *.bar; do true; done; done" +-- >>> prop $ verify checkLoopVariableReassignment "for i in *; do for i in *.bar; do true; done; done" +-- >>> prop $ verify checkLoopVariableReassignment "for i in *; do for((i=0; i<3; i++)); do true; done; done" +-- >>> prop $ verifyNot checkLoopVariableReassignment "for i in *; do for j in *.bar; do true; done; done" checkLoopVariableReassignment params token = potentially $ case token of T_ForIn {} -> check @@ -2618,11 +2619,11 @@ checkLoopVariableReassignment params token = _ -> fail "not loop" -- | --- prop> verify checkTrailingBracket "if -z n ]]; then true; fi " --- prop> verifyNot checkTrailingBracket "if [[ -z n ]]; then true; fi " --- prop> verify checkTrailingBracket "a || b ] && thing" --- prop> verifyNot checkTrailingBracket "run [ foo ]" --- prop> verifyNot checkTrailingBracket "run bar ']'" +-- >>> prop $ verify checkTrailingBracket "if -z n ]]; then true; fi " +-- >>> prop $ verifyNot checkTrailingBracket "if [[ -z n ]]; then true; fi " +-- >>> prop $ verify checkTrailingBracket "a || b ] && thing" +-- >>> prop $ verifyNot checkTrailingBracket "run [ foo ]" +-- >>> prop $ verifyNot checkTrailingBracket "run bar ']'" checkTrailingBracket _ token = case token of T_SimpleCommand _ _ tokens@(_:_) -> check (last tokens) token @@ -2645,15 +2646,15 @@ checkTrailingBracket _ token = x -> x -- | --- prop> verify checkReturnAgainstZero "[ $? -eq 0 ]" --- prop> verify checkReturnAgainstZero "[[ \"$?\" -gt 0 ]]" --- prop> verify checkReturnAgainstZero "[[ 0 -ne $? ]]" --- prop> verifyNot checkReturnAgainstZero "[[ $? -eq 4 ]]" --- prop> verify checkReturnAgainstZero "[[ 0 -eq $? ]]" --- prop> verifyNot checkReturnAgainstZero "[[ $R -eq 0 ]]" --- prop> verify checkReturnAgainstZero "(( $? == 0 ))" --- prop> verify checkReturnAgainstZero "(( $? ))" --- prop> verify checkReturnAgainstZero "(( ! $? ))" +-- >>> prop $ verify checkReturnAgainstZero "[ $? -eq 0 ]" +-- >>> prop $ verify checkReturnAgainstZero "[[ \"$?\" -gt 0 ]]" +-- >>> prop $ verify checkReturnAgainstZero "[[ 0 -ne $? ]]" +-- >>> prop $ verifyNot checkReturnAgainstZero "[[ $? -eq 4 ]]" +-- >>> prop $ verify checkReturnAgainstZero "[[ 0 -eq $? ]]" +-- >>> prop $ verifyNot checkReturnAgainstZero "[[ $R -eq 0 ]]" +-- >>> prop $ verify checkReturnAgainstZero "(( $? == 0 ))" +-- >>> prop $ verify checkReturnAgainstZero "(( $? ))" +-- >>> prop $ verify checkReturnAgainstZero "(( ! $? ))" checkReturnAgainstZero _ token = case token of TC_Binary id _ _ lhs rhs -> check lhs rhs @@ -2676,14 +2677,14 @@ checkReturnAgainstZero _ token = message id = style id 2181 "Check exit code directly with e.g. 'if mycmd;', not indirectly with $?." -- | --- prop> verify checkRedirectedNowhere "> file" --- prop> verify checkRedirectedNowhere "> file | grep foo" --- prop> verify checkRedirectedNowhere "grep foo | > bar" --- prop> verifyNot checkRedirectedNowhere "grep foo > bar" --- prop> verifyNot checkRedirectedNowhere "foo | grep bar > baz" --- prop> verifyNot checkRedirectedNowhere "var=$(value) 2> /dev/null" --- prop> verifyNot checkRedirectedNowhere "var=$(< file)" --- prop> verifyNot checkRedirectedNowhere "var=`< file`" +-- >>> prop $ verify checkRedirectedNowhere "> file" +-- >>> prop $ verify checkRedirectedNowhere "> file | grep foo" +-- >>> prop $ verify checkRedirectedNowhere "grep foo | > bar" +-- >>> prop $ verifyNot checkRedirectedNowhere "grep foo > bar" +-- >>> prop $ verifyNot checkRedirectedNowhere "foo | grep bar > baz" +-- >>> prop $ verifyNot checkRedirectedNowhere "var=$(value) 2> /dev/null" +-- >>> prop $ verifyNot checkRedirectedNowhere "var=$(< file)" +-- >>> prop $ verifyNot checkRedirectedNowhere "var=`< file`" checkRedirectedNowhere params token = case token of T_Pipeline _ _ [single] -> potentially $ do @@ -2710,15 +2711,15 @@ checkRedirectedNowhere params token = -- | --- prop> verifyTree checkArrayAssignmentIndices "declare -A foo; foo=(bar)" --- prop> verifyNotTree checkArrayAssignmentIndices "declare -a foo; foo=(bar)" --- prop> verifyNotTree checkArrayAssignmentIndices "declare -A foo; foo=([i]=bar)" --- prop> verifyTree checkArrayAssignmentIndices "typeset -A foo; foo+=(bar)" --- prop> verifyTree checkArrayAssignmentIndices "arr=( [foo]= bar )" --- prop> verifyTree checkArrayAssignmentIndices "arr=( [foo] = bar )" --- prop> verifyTree checkArrayAssignmentIndices "arr=( var=value )" --- prop> verifyNotTree checkArrayAssignmentIndices "arr=( [foo]=bar )" --- prop> verifyNotTree checkArrayAssignmentIndices "arr=( [foo]=\"\" )" +-- >>> prop $ verifyTree checkArrayAssignmentIndices "declare -A foo; foo=(bar)" +-- >>> prop $ verifyNotTree checkArrayAssignmentIndices "declare -a foo; foo=(bar)" +-- >>> prop $ verifyNotTree checkArrayAssignmentIndices "declare -A foo; foo=([i]=bar)" +-- >>> prop $ verifyTree checkArrayAssignmentIndices "typeset -A foo; foo+=(bar)" +-- >>> prop $ verifyTree checkArrayAssignmentIndices "arr=( [foo]= bar )" +-- >>> prop $ verifyTree checkArrayAssignmentIndices "arr=( [foo] = bar )" +-- >>> prop $ verifyTree checkArrayAssignmentIndices "arr=( var=value )" +-- >>> prop $ verifyNotTree checkArrayAssignmentIndices "arr=( [foo]=bar )" +-- >>> prop $ verifyNotTree checkArrayAssignmentIndices "arr=( [foo]=\"\" )" checkArrayAssignmentIndices params root = runNodeAnalysis check params root where @@ -2753,15 +2754,15 @@ checkArrayAssignmentIndices params root = _ -> return () -- | --- prop> verify checkUnmatchableCases "case foo in bar) true; esac" --- prop> verify checkUnmatchableCases "case foo-$bar in ??|*) true; esac" --- prop> verify checkUnmatchableCases "case foo in foo) true; esac" --- prop> verifyNot checkUnmatchableCases "case foo-$bar in foo*|*bar|*baz*) true; esac" --- prop> verify checkUnmatchableCases "case $f in *.txt) true;; f??.txt) false;; esac" --- prop> verifyNot checkUnmatchableCases "case $f in ?*) true;; *) false;; esac" --- prop> verifyNot checkUnmatchableCases "case $f in $(x)) true;; asdf) false;; esac" --- prop> verify checkUnmatchableCases "case $f in cow) true;; bar|cow) false;; esac" --- prop> verifyNot checkUnmatchableCases "case $f in x) true;;& x) false;; esac" +-- >>> prop $ verify checkUnmatchableCases "case foo in bar) true; esac" +-- >>> prop $ verify checkUnmatchableCases "case foo-$bar in ??|*) true; esac" +-- >>> prop $ verify checkUnmatchableCases "case foo in foo) true; esac" +-- >>> prop $ verifyNot checkUnmatchableCases "case foo-$bar in foo*|*bar|*baz*) true; esac" +-- >>> prop $ verify checkUnmatchableCases "case $f in *.txt) true;; f??.txt) false;; esac" +-- >>> prop $ verifyNot checkUnmatchableCases "case $f in ?*) true;; *) false;; esac" +-- >>> prop $ verifyNot checkUnmatchableCases "case $f in $(x)) true;; asdf) false;; esac" +-- >>> prop $ verify checkUnmatchableCases "case $f in cow) true;; bar|cow) false;; esac" +-- >>> prop $ verifyNot checkUnmatchableCases "case $f in x) true;;& x) false;; esac" checkUnmatchableCases _ t = case t of T_CaseExpression _ word list -> do @@ -2808,13 +2809,13 @@ checkUnmatchableCases _ t = -- | --- prop> verify checkSubshellAsTest "( -e file )" --- prop> verify checkSubshellAsTest "( 1 -gt 2 )" --- prop> verifyNot checkSubshellAsTest "( grep -c foo bar )" --- prop> verifyNot checkSubshellAsTest "[ 1 -gt 2 ]" --- prop> verify checkSubshellAsTest "( -e file && -x file )" --- prop> verify checkSubshellAsTest "( -e file || -x file && -t 1 )" --- prop> verify checkSubshellAsTest "( ! -d file )" +-- >>> prop $ verify checkSubshellAsTest "( -e file )" +-- >>> prop $ verify checkSubshellAsTest "( 1 -gt 2 )" +-- >>> prop $ verifyNot checkSubshellAsTest "( grep -c foo bar )" +-- >>> prop $ verifyNot checkSubshellAsTest "[ 1 -gt 2 ]" +-- >>> prop $ verify checkSubshellAsTest "( -e file && -x file )" +-- >>> prop $ verify checkSubshellAsTest "( -e file || -x file && -t 1 )" +-- >>> prop $ verify checkSubshellAsTest "( ! -d file )" checkSubshellAsTest _ t = case t of T_Subshell id [w] -> check id w @@ -2837,14 +2838,14 @@ checkSubshellAsTest _ t = -- | --- prop> verify checkSplittingInArrays "a=( $var )" --- prop> verify checkSplittingInArrays "a=( $(cmd) )" --- prop> verifyNot checkSplittingInArrays "a=( \"$var\" )" --- prop> verifyNot checkSplittingInArrays "a=( \"$(cmd)\" )" --- prop> verifyNot checkSplittingInArrays "a=( $! $$ $# )" --- prop> verifyNot checkSplittingInArrays "a=( ${#arr[@]} )" --- prop> verifyNot checkSplittingInArrays "a=( foo{1,2} )" --- prop> verifyNot checkSplittingInArrays "a=( * )" +-- >>> prop $ verify checkSplittingInArrays "a=( $var )" +-- >>> prop $ verify checkSplittingInArrays "a=( $(cmd) )" +-- >>> prop $ verifyNot checkSplittingInArrays "a=( \"$var\" )" +-- >>> prop $ verifyNot checkSplittingInArrays "a=( \"$(cmd)\" )" +-- >>> prop $ verifyNot checkSplittingInArrays "a=( $! $$ $# )" +-- >>> prop $ verifyNot checkSplittingInArrays "a=( ${#arr[@]} )" +-- >>> prop $ verifyNot checkSplittingInArrays "a=( foo{1,2} )" +-- >>> prop $ verifyNot checkSplittingInArrays "a=( * )" checkSplittingInArrays params t = case t of T_Array _ elements -> mapM_ check elements @@ -2875,10 +2876,10 @@ checkSplittingInArrays params t = -- | --- prop> verify checkRedirectionToNumber "( 1 > 2 )" --- prop> verify checkRedirectionToNumber "foo 1>2" --- prop> verifyNot checkRedirectionToNumber "echo foo > '2'" --- prop> verifyNot checkRedirectionToNumber "foo 1>&2" +-- >>> prop $ verify checkRedirectionToNumber "( 1 > 2 )" +-- >>> prop $ verify checkRedirectionToNumber "foo 1>2" +-- >>> prop $ verifyNot checkRedirectionToNumber "echo foo > '2'" +-- >>> prop $ verifyNot checkRedirectionToNumber "foo 1>&2" checkRedirectionToNumber _ t = case t of T_IoFile id _ word -> potentially $ do file <- getUnquotedLiteral word @@ -2887,9 +2888,9 @@ checkRedirectionToNumber _ t = case t of _ -> return () -- | --- prop> verify checkGlobAsCommand "foo*" --- prop> verify checkGlobAsCommand "$(var[i])" --- prop> verifyNot checkGlobAsCommand "echo foo*" +-- >>> prop $ verify checkGlobAsCommand "foo*" +-- >>> prop $ verify checkGlobAsCommand "$(var[i])" +-- >>> prop $ verifyNot checkGlobAsCommand "echo foo*" checkGlobAsCommand _ t = case t of T_SimpleCommand _ _ (first:_) -> when (isGlob first) $ @@ -2898,10 +2899,10 @@ checkGlobAsCommand _ t = case t of -- | --- prop> verify checkFlagAsCommand "-e file" --- prop> verify checkFlagAsCommand "foo\n --bar=baz" --- prop> verifyNot checkFlagAsCommand "'--myexec--' args" --- prop> verifyNot checkFlagAsCommand "var=cmd --arg" -- Handled by SC2037 +-- >>> prop $ verify checkFlagAsCommand "-e file" +-- >>> prop $ verify checkFlagAsCommand "foo\n --bar=baz" +-- >>> prop $ verifyNot checkFlagAsCommand "'--myexec--' args" +-- >>> prop $ verifyNot checkFlagAsCommand "var=cmd --arg" -- Handled by SC2037 checkFlagAsCommand _ t = case t of T_SimpleCommand _ [] (first:_) -> when (isUnquotedFlag first) $ @@ -2910,22 +2911,22 @@ checkFlagAsCommand _ t = case t of -- | --- prop> verify checkEmptyCondition "if [ ]; then ..; fi" --- prop> verifyNot checkEmptyCondition "[ foo -o bar ]" +-- >>> prop $ verify checkEmptyCondition "if [ ]; then ..; fi" +-- >>> prop $ verifyNot checkEmptyCondition "[ foo -o bar ]" checkEmptyCondition _ t = case t of TC_Empty id _ -> style id 2212 "Use 'false' instead of empty [/[[ conditionals." _ -> return () -- | --- prop> verify checkPipeToNowhere "foo | echo bar" --- prop> verify checkPipeToNowhere "basename < file.txt" --- prop> verify checkPipeToNowhere "printf 'Lol' <<< str" --- prop> verify checkPipeToNowhere "printf 'Lol' << eof\nlol\neof\n" --- prop> verifyNot checkPipeToNowhere "echo foo | xargs du" --- prop> verifyNot checkPipeToNowhere "ls | echo $(cat)" --- prop> verifyNot checkPipeToNowhere "echo foo | var=$(cat) ls" --- prop> verify checkPipeToNowhere "foo | true" --- prop> verifyNot checkPipeToNowhere "mv -i f . < /dev/stdin" +-- >>> prop $ verify checkPipeToNowhere "foo | echo bar" +-- >>> prop $ verify checkPipeToNowhere "basename < file.txt" +-- >>> prop $ verify checkPipeToNowhere "printf 'Lol' <<< str" +-- >>> prop $ verify checkPipeToNowhere "printf 'Lol' << eof\nlol\neof\n" +-- >>> prop $ verifyNot checkPipeToNowhere "echo foo | xargs du" +-- >>> prop $ verifyNot checkPipeToNowhere "ls | echo $(cat)" +-- >>> prop $ verifyNot checkPipeToNowhere "echo foo | var=$(cat) ls" +-- >>> prop $ verify checkPipeToNowhere "foo | true" +-- >>> prop $ verifyNot checkPipeToNowhere "mv -i f . < /dev/stdin" checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity () checkPipeToNowhere _ t = case t of @@ -2978,10 +2979,10 @@ checkPipeToNowhere _ t = _ -> False -- | --- prop> verifyTree checkUseBeforeDefinition "f; f() { true; }" --- prop> verifyNotTree checkUseBeforeDefinition "f() { true; }; f" --- prop> verifyNotTree checkUseBeforeDefinition "if ! mycmd --version; then mycmd() { true; }; fi" --- prop> verifyNotTree checkUseBeforeDefinition "mycmd || mycmd() { f; }" +-- >>> prop $ verifyTree checkUseBeforeDefinition "f; f() { true; }" +-- >>> prop $ verifyNotTree checkUseBeforeDefinition "f() { true; }; f" +-- >>> prop $ verifyNotTree checkUseBeforeDefinition "if ! mycmd --version; then mycmd() { true; }; fi" +-- >>> prop $ verifyNotTree checkUseBeforeDefinition "mycmd || mycmd() { f; }" checkUseBeforeDefinition _ t = execWriter $ evalStateT (mapM_ examine $ revCommands) Map.empty where @@ -3010,9 +3011,9 @@ checkUseBeforeDefinition _ t = else concatMap recursiveSequences list -- | --- prop> verify checkForLoopGlobVariables "for i in $var/*.txt; do true; done" --- prop> verifyNot checkForLoopGlobVariables "for i in \"$var\"/*.txt; do true; done" --- prop> verifyNot checkForLoopGlobVariables "for i in $var; do true; done" +-- >>> prop $ verify checkForLoopGlobVariables "for i in $var/*.txt; do true; done" +-- >>> prop $ verifyNot checkForLoopGlobVariables "for i in \"$var\"/*.txt; do true; done" +-- >>> prop $ verifyNot checkForLoopGlobVariables "for i in $var; do true; done" checkForLoopGlobVariables _ t = case t of T_ForIn _ _ words _ -> mapM_ check words @@ -3025,10 +3026,10 @@ checkForLoopGlobVariables _ t = "Quote expansions in this for loop glob to prevent wordsplitting, e.g. \"$dir\"/*.txt ." -- | --- prop> verify checkSubshelledTests "a && ( [ b ] || ! [ c ] )" --- prop> verify checkSubshelledTests "( [ a ] )" --- prop> verify checkSubshelledTests "( [ a ] && [ b ] || test c )" --- prop> verify checkSubshelledTests "( [ a ] && { [ b ] && [ c ]; } )" +-- >>> prop $ verify checkSubshelledTests "a && ( [ b ] || ! [ c ] )" +-- >>> prop $ verify checkSubshelledTests "( [ a ] )" +-- >>> prop $ verify checkSubshelledTests "( [ a ] && [ b ] || test c )" +-- >>> prop $ verify checkSubshelledTests "( [ a ] && { [ b ] && [ c ]; } )" checkSubshelledTests params t = case t of T_Subshell id list | all isTestStructure list -> @@ -3090,11 +3091,11 @@ checkSubshelledTests params t = _ -> False -- | --- prop> verify checkInvertedStringTest "[ ! -z $var ]" --- prop> verify checkInvertedStringTest "! [[ -n $var ]]" --- prop> verifyNot checkInvertedStringTest "! [ -x $var ]" --- prop> verifyNot checkInvertedStringTest "[[ ! -w $var ]]" --- prop> verifyNot checkInvertedStringTest "[ -z $var ]" +-- >>> prop $ verify checkInvertedStringTest "[ ! -z $var ]" +-- >>> prop $ verify checkInvertedStringTest "! [[ -n $var ]]" +-- >>> prop $ verifyNot checkInvertedStringTest "! [ -x $var ]" +-- >>> prop $ verifyNot checkInvertedStringTest "[[ ! -w $var ]]" +-- >>> prop $ verifyNot checkInvertedStringTest "[ -z $var ]" checkInvertedStringTest _ t = case t of TC_Unary _ _ "!" (TC_Unary _ _ op _) -> diff --git a/src/ShellCheck/AnalyzerLib.hs b/src/ShellCheck/AnalyzerLib.hs index bb3ebb3..09f40a3 100644 --- a/src/ShellCheck/AnalyzerLib.hs +++ b/src/ShellCheck/AnalyzerLib.hs @@ -37,6 +37,10 @@ import qualified Data.Map as Map import Data.Maybe import Data.Semigroup +prop :: Bool -> IO () +prop False = putStrLn "FAIL" +prop True = return () + type Analysis = AnalyzerM () type AnalyzerM a = RWS Parameters [TokenComment] Cache a nullCheck = const $ return () @@ -193,14 +197,14 @@ containsLastpipe root = -- | --- prop> determineShell (fromJust $ pScript "#!/bin/sh") == Sh --- prop> determineShell (fromJust $ pScript "#!/usr/bin/env ksh") == Ksh --- prop> determineShell (fromJust $ pScript "") == Bash --- prop> determineShell (fromJust $ pScript "#!/bin/sh -e") == Sh --- prop> determineShell (fromJust $ pScript "#!/bin/ksh\n#shellcheck shell=sh\nfoo") == Sh --- prop> determineShell (fromJust $ pScript "#shellcheck shell=sh\nfoo") == Sh --- prop> determineShell (fromJust $ pScript "#! /bin/sh") == Sh --- prop> determineShell (fromJust $ pScript "#! /bin/ash") == Dash +-- >>> prop $ determineShell (fromJust $ pScript "#!/bin/sh") == Sh +-- >>> prop $ determineShell (fromJust $ pScript "#!/usr/bin/env ksh") == Ksh +-- >>> prop $ determineShell (fromJust $ pScript "") == Bash +-- >>> prop $ determineShell (fromJust $ pScript "#!/bin/sh -e") == Sh +-- >>> prop $ determineShell (fromJust $ pScript "#!/bin/ksh\n#shellcheck shell=sh\nfoo") == Sh +-- >>> prop $ determineShell (fromJust $ pScript "#shellcheck shell=sh\nfoo") == Sh +-- >>> prop $ determineShell (fromJust $ pScript "#! /bin/sh") == Sh +-- >>> prop $ determineShell (fromJust $ pScript "#! /bin/ash") == Dash determineShell t = fromMaybe Bash $ do shellString <- foldl mplus Nothing $ getCandidates t shellForExecutable shellString @@ -623,10 +627,10 @@ getIndexReferences s = fromMaybe [] $ do re = mkRegex "(\\[.*\\])" -- | --- prop> getOffsetReferences ":bar" == ["bar"] --- prop> getOffsetReferences ":bar:baz" == ["bar", "baz"] --- prop> getOffsetReferences "[foo]:bar" == ["bar"] --- prop> getOffsetReferences "[foo]:bar:baz" == ["bar", "baz"] +-- >>> prop $ getOffsetReferences ":bar" == ["bar"] +-- >>> prop $ getOffsetReferences ":bar:baz" == ["bar", "baz"] +-- >>> prop $ getOffsetReferences "[foo]:bar" == ["bar"] +-- >>> prop $ getOffsetReferences "[foo]:bar:baz" == ["bar", "baz"] getOffsetReferences mods = fromMaybe [] $ do -- if mods start with [, then drop until ] match <- matchRegex re mods @@ -720,9 +724,9 @@ isVariableChar x = isVariableStartChar x || isDigit x variableNameRegex = mkRegex "[_a-zA-Z][_a-zA-Z0-9]*" -- | --- prop> isVariableName "_fo123" --- prop> not $ isVariableName "4" --- prop> not $ isVariableName "test: " +-- >>> prop $ isVariableName "_fo123" +-- >>> prop $ not $ isVariableName "4" +-- >>> prop $ not $ isVariableName "test: " isVariableName (x:r) = isVariableStartChar x && all isVariableChar r isVariableName _ = False @@ -731,7 +735,7 @@ getVariablesFromLiteralToken token = -- Try to get referenced variables from a literal string like "$foo" -- Ignores tons of cases like arithmetic evaluation and array indices. --- prop> getVariablesFromLiteral "$foo${bar//a/b}$BAZ" == ["foo", "bar", "BAZ"] +-- >>> prop $ getVariablesFromLiteral "$foo${bar//a/b}$BAZ" == ["foo", "bar", "BAZ"] getVariablesFromLiteral string = map (!! 0) $ matchAllSubgroups variableRegex string where @@ -740,19 +744,19 @@ getVariablesFromLiteral string = -- | -- Get the variable name from an expansion like ${var:-foo} -- --- prop> getBracedReference "foo" == "foo" --- prop> getBracedReference "#foo" == "foo" --- prop> getBracedReference "#" == "#" --- prop> getBracedReference "##" == "#" --- prop> getBracedReference "#!" == "!" --- prop> getBracedReference "!#" == "#" --- prop> getBracedReference "!foo#?" == "foo" --- prop> getBracedReference "foo-bar" == "foo" --- prop> getBracedReference "foo:-bar" == "foo" --- prop> getBracedReference "foo: -1" == "foo" --- prop> getBracedReference "!os*" == "" --- prop> getBracedReference "!os?bar**" == "" --- prop> getBracedReference "foo[bar]" == "foo" +-- >>> prop $ getBracedReference "foo" == "foo" +-- >>> prop $ getBracedReference "#foo" == "foo" +-- >>> prop $ getBracedReference "#" == "#" +-- >>> prop $ getBracedReference "##" == "#" +-- >>> prop $ getBracedReference "#!" == "!" +-- >>> prop $ getBracedReference "!#" == "#" +-- >>> prop $ getBracedReference "!foo#?" == "foo" +-- >>> prop $ getBracedReference "foo-bar" == "foo" +-- >>> prop $ getBracedReference "foo:-bar" == "foo" +-- >>> prop $ getBracedReference "foo: -1" == "foo" +-- >>> prop $ getBracedReference "!os*" == "" +-- >>> prop $ getBracedReference "!os?bar**" == "" +-- >>> prop $ getBracedReference "foo[bar]" == "foo" getBracedReference s = fromMaybe s $ nameExpansion s `mplus` takeName noPrefix `mplus` getSpecial noPrefix `mplus` getSpecial s where @@ -776,9 +780,9 @@ getBracedReference s = fromMaybe s $ nameExpansion _ = Nothing -- | --- prop> getBracedModifier "foo:bar:baz" == ":bar:baz" --- prop> getBracedModifier "!var:-foo" == ":-foo" --- prop> getBracedModifier "foo[bar]" == "[bar]" +-- >>> prop $ getBracedModifier "foo:bar:baz" == ":bar:baz" +-- >>> prop $ getBracedModifier "!var:-foo" == ":-foo" +-- >>> prop $ getBracedModifier "foo[bar]" == "[bar]" getBracedModifier s = fromMaybe "" . listToMaybe $ do let var = getBracedReference s a <- dropModifier s diff --git a/src/ShellCheck/Checks/Commands.hs b/src/ShellCheck/Checks/Commands.hs index 4858a0f..7d603d3 100644 --- a/src/ShellCheck/Checks/Commands.hs +++ b/src/ShellCheck/Checks/Commands.hs @@ -42,7 +42,6 @@ data CommandName = Exactly String | Basename String data CommandCheck = CommandCheck CommandName (Token -> Analysis) - verify :: CommandCheck -> String -> Bool verify f s = producesComments (getChecker [f]) s == Just True verifyNot f s = producesComments (getChecker [f]) s == Just False @@ -125,20 +124,20 @@ checker :: Parameters -> Checker checker params = getChecker commandChecks -- | --- prop> verify checkTr "tr [a-f] [A-F]" --- prop> verify checkTr "tr 'a-z' 'A-Z'" --- prop> verify checkTr "tr '[a-z]' '[A-Z]'" --- prop> verifyNot checkTr "tr -d '[:lower:]'" --- prop> verifyNot checkTr "tr -d '[:upper:]'" --- prop> verifyNot checkTr "tr -d '|/_[:upper:]'" --- prop> verifyNot checkTr "ls [a-z]" --- prop> verify checkTr "tr foo bar" --- prop> verify checkTr "tr 'hello' 'world'" --- prop> verifyNot checkTr "tr aeiou _____" --- prop> verifyNot checkTr "a-z n-za-m" --- prop> verifyNot checkTr "tr --squeeze-repeats rl lr" --- prop> verifyNot checkTr "tr abc '[d*]'" --- prop> verifyNot checkTr "tr '[=e=]' 'e'" +-- >>> prop $ verify checkTr "tr [a-f] [A-F]" +-- >>> prop $ verify checkTr "tr 'a-z' 'A-Z'" +-- >>> prop $ verify checkTr "tr '[a-z]' '[A-Z]'" +-- >>> prop $ verifyNot checkTr "tr -d '[:lower:]'" +-- >>> prop $ verifyNot checkTr "tr -d '[:upper:]'" +-- >>> prop $ verifyNot checkTr "tr -d '|/_[:upper:]'" +-- >>> prop $ verifyNot checkTr "ls [a-z]" +-- >>> prop $ verify checkTr "tr foo bar" +-- >>> prop $ verify checkTr "tr 'hello' 'world'" +-- >>> prop $ verifyNot checkTr "tr aeiou _____" +-- >>> prop $ verifyNot checkTr "a-z n-za-m" +-- >>> prop $ verifyNot checkTr "tr --squeeze-repeats rl lr" +-- >>> prop $ verifyNot checkTr "tr abc '[d*]'" +-- >>> prop $ verifyNot checkTr "tr '[=e=]' 'e'" checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments) where f w | isGlob w = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme? @@ -160,9 +159,9 @@ checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments) in relevant /= nub relevant -- | --- prop> verify checkFindNameGlob "find / -name *.php" --- prop> verify checkFindNameGlob "find / -type f -ipath *(foo)" --- prop> verifyNot checkFindNameGlob "find * -name '*.php'" +-- >>> prop $ verify checkFindNameGlob "find / -name *.php" +-- >>> prop $ verify checkFindNameGlob "find / -type f -ipath *(foo)" +-- >>> prop $ verifyNot checkFindNameGlob "find * -name '*.php'" checkFindNameGlob = CommandCheck (Basename "find") (f . arguments) where acceptsGlob (Just s) = s `elem` [ "-ilname", "-iname", "-ipath", "-iregex", "-iwholename", "-lname", "-name", "-path", "-regex", "-wholename" ] acceptsGlob _ = False @@ -176,10 +175,10 @@ checkFindNameGlob = CommandCheck (Basename "find") (f . arguments) where -- | --- prop> verify checkNeedlessExpr "foo=$(expr 3 + 2)" --- prop> verify checkNeedlessExpr "foo=`echo \\`expr 3 + 2\\``" --- prop> verifyNot checkNeedlessExpr "foo=$(expr foo : regex)" --- prop> verifyNot checkNeedlessExpr "foo=$(expr foo \\< regex)" +-- >>> prop $ verify checkNeedlessExpr "foo=$(expr 3 + 2)" +-- >>> prop $ verify checkNeedlessExpr "foo=`echo \\`expr 3 + 2\\``" +-- >>> prop $ verifyNot checkNeedlessExpr "foo=$(expr foo : regex)" +-- >>> prop $ verifyNot checkNeedlessExpr "foo=$(expr foo \\< regex)" checkNeedlessExpr = CommandCheck (Basename "expr") f where f t = when (all (`notElem` exceptions) (words $ arguments t)) $ @@ -191,21 +190,21 @@ checkNeedlessExpr = CommandCheck (Basename "expr") f where -- | --- prop> verify checkGrepRe "cat foo | grep *.mp3" --- prop> verify checkGrepRe "grep -Ev cow*test *.mp3" --- prop> verify checkGrepRe "grep --regex=*.mp3 file" --- prop> verifyNot checkGrepRe "grep foo *.mp3" --- prop> verifyNot checkGrepRe "grep-v --regex=moo *" --- prop> verifyNot checkGrepRe "grep foo \\*.mp3" --- prop> verify checkGrepRe "grep *foo* file" --- prop> verify checkGrepRe "ls | grep foo*.jpg" --- prop> verifyNot checkGrepRe "grep '[0-9]*' file" --- prop> verifyNot checkGrepRe "grep '^aa*' file" --- prop> verifyNot checkGrepRe "grep --include=*.png foo" --- prop> verifyNot checkGrepRe "grep -F 'Foo*' file" --- prop> verifyNot checkGrepRe "grep -- -foo bar*" --- prop> verifyNot checkGrepRe "grep -e -foo bar*" --- prop> verifyNot checkGrepRe "grep --regex -foo bar*" +-- >>> prop $ verify checkGrepRe "cat foo | grep *.mp3" +-- >>> prop $ verify checkGrepRe "grep -Ev cow*test *.mp3" +-- >>> prop $ verify checkGrepRe "grep --regex=*.mp3 file" +-- >>> prop $ verifyNot checkGrepRe "grep foo *.mp3" +-- >>> prop $ verifyNot checkGrepRe "grep-v --regex=moo *" +-- >>> prop $ verifyNot checkGrepRe "grep foo \\*.mp3" +-- >>> prop $ verify checkGrepRe "grep *foo* file" +-- >>> prop $ verify checkGrepRe "ls | grep foo*.jpg" +-- >>> prop $ verifyNot checkGrepRe "grep '[0-9]*' file" +-- >>> prop $ verifyNot checkGrepRe "grep '^aa*' file" +-- >>> prop $ verifyNot checkGrepRe "grep --include=*.png foo" +-- >>> prop $ verifyNot checkGrepRe "grep -F 'Foo*' file" +-- >>> prop $ verifyNot checkGrepRe "grep -- -foo bar*" +-- >>> prop $ verifyNot checkGrepRe "grep -e -foo bar*" +-- >>> prop $ verifyNot checkGrepRe "grep --regex -foo bar*" checkGrepRe = CommandCheck (Basename "grep") check where check cmd = f cmd (arguments cmd) @@ -257,10 +256,10 @@ checkGrepRe = CommandCheck (Basename "grep") check where -- | --- prop> verify checkTrapQuotes "trap \"echo $num\" INT" --- prop> verify checkTrapQuotes "trap \"echo `ls`\" INT" --- prop> verifyNot checkTrapQuotes "trap 'echo $num' INT" --- prop> verify checkTrapQuotes "trap \"echo $((1+num))\" EXIT DEBUG" +-- >>> prop $ verify checkTrapQuotes "trap \"echo $num\" INT" +-- >>> prop $ verify checkTrapQuotes "trap \"echo `ls`\" INT" +-- >>> prop $ verifyNot checkTrapQuotes "trap 'echo $num' INT" +-- >>> prop $ verify checkTrapQuotes "trap \"echo $((1+num))\" EXIT DEBUG" checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where f (x:_) = checkTrap x f _ = return () @@ -275,13 +274,13 @@ checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where -- | --- prop> verifyNot checkReturn "return" --- prop> verifyNot checkReturn "return 1" --- prop> verifyNot checkReturn "return $var" --- prop> verifyNot checkReturn "return $((a|b))" --- prop> verify checkReturn "return -1" --- prop> verify checkReturn "return 1000" --- prop> verify checkReturn "return 'hello world'" +-- >>> prop $ verifyNot checkReturn "return" +-- >>> prop $ verifyNot checkReturn "return 1" +-- >>> prop $ verifyNot checkReturn "return $var" +-- >>> prop $ verifyNot checkReturn "return $((a|b))" +-- >>> prop $ verify checkReturn "return -1" +-- >>> prop $ verify checkReturn "return 1000" +-- >>> prop $ verify checkReturn "return 'hello world'" checkReturn = CommandCheck (Exactly "return") (f . arguments) where f (first:second:_) = @@ -305,9 +304,9 @@ checkReturn = CommandCheck (Exactly "return") (f . arguments) -- | --- prop> verify checkFindExecWithSingleArgument "find . -exec 'cat {} | wc -l' \\;" --- prop> verify checkFindExecWithSingleArgument "find . -execdir 'cat {} | wc -l' +" --- prop> verifyNot checkFindExecWithSingleArgument "find . -exec wc -l {} \\;" +-- >>> prop $ verify checkFindExecWithSingleArgument "find . -exec 'cat {} | wc -l' \\;" +-- >>> prop $ verify checkFindExecWithSingleArgument "find . -execdir 'cat {} | wc -l' +" +-- >>> prop $ verifyNot checkFindExecWithSingleArgument "find . -exec wc -l {} \\;" checkFindExecWithSingleArgument = CommandCheck (Basename "find") (f . arguments) where f = void . sequence . mapMaybe check . tails @@ -324,11 +323,11 @@ checkFindExecWithSingleArgument = CommandCheck (Basename "find") (f . arguments) -- | --- prop> verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n'" --- prop> verifyNot checkUnusedEchoEscapes "echo -e 'foi\\nbar'" --- prop> verify checkUnusedEchoEscapes "echo \"n:\\t42\"" --- prop> verifyNot checkUnusedEchoEscapes "echo lol" --- prop> verifyNot checkUnusedEchoEscapes "echo -n -e '\n'" +-- >>> prop $ verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n'" +-- >>> prop $ verifyNot checkUnusedEchoEscapes "echo -e 'foi\\nbar'" +-- >>> prop $ verify checkUnusedEchoEscapes "echo \"n:\\t42\"" +-- >>> prop $ verifyNot checkUnusedEchoEscapes "echo lol" +-- >>> prop $ verifyNot checkUnusedEchoEscapes "echo -n -e '\n'" checkUnusedEchoEscapes = CommandCheck (Basename "echo") f where hasEscapes = mkRegex "\\\\[rnt]" @@ -344,9 +343,9 @@ checkUnusedEchoEscapes = CommandCheck (Basename "echo") f -- | --- prop> verify checkInjectableFindSh "find . -exec sh -c 'echo {}' \\;" --- prop> verify checkInjectableFindSh "find . -execdir bash -c 'rm \"{}\"' ';'" --- prop> verifyNot checkInjectableFindSh "find . -exec sh -c 'rm \"$@\"' _ {} \\;" +-- >>> prop $ verify checkInjectableFindSh "find . -exec sh -c 'echo {}' \\;" +-- >>> prop $ verify checkInjectableFindSh "find . -execdir bash -c 'rm \"{}\"' ';'" +-- >>> prop $ verifyNot checkInjectableFindSh "find . -exec sh -c 'rm \"$@\"' _ {} \\;" checkInjectableFindSh = CommandCheck (Basename "find") (check . arguments) where check args = do @@ -370,9 +369,9 @@ checkInjectableFindSh = CommandCheck (Basename "find") (check . arguments) -- | --- prop> verify checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au' -exec rm {} +" --- prop> verifyNot checkFindActionPrecedence "find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)" --- prop> verifyNot checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au'" +-- >>> prop $ verify checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au' -exec rm {} +" +-- >>> prop $ verifyNot checkFindActionPrecedence "find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)" +-- >>> prop $ verifyNot checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au'" checkFindActionPrecedence = CommandCheck (Basename "find") (f . arguments) where pattern = [isMatch, const True, isParam ["-o", "-or"], isMatch, const True, isAction] @@ -390,28 +389,28 @@ checkFindActionPrecedence = CommandCheck (Basename "find") (f . arguments) -- | --- prop> verify checkMkdirDashPM "mkdir -p -m 0755 a/b" --- prop> verify checkMkdirDashPM "mkdir -pm 0755 $dir" --- prop> verify checkMkdirDashPM "mkdir -vpm 0755 a/b" --- prop> verify checkMkdirDashPM "mkdir -pm 0755 -v a/b" --- prop> verify checkMkdirDashPM "mkdir --parents --mode=0755 a/b" --- prop> verify checkMkdirDashPM "mkdir --parents --mode 0755 a/b" --- prop> verify checkMkdirDashPM "mkdir -p --mode=0755 a/b" --- prop> verify checkMkdirDashPM "mkdir --parents -m 0755 a/b" --- prop> verifyNot checkMkdirDashPM "mkdir -p a/b" --- prop> verifyNot checkMkdirDashPM "mkdir -m 0755 a/b" --- prop> verifyNot checkMkdirDashPM "mkdir a/b" --- prop> verifyNot checkMkdirDashPM "mkdir --parents a/b" --- prop> verifyNot checkMkdirDashPM "mkdir --mode=0755 a/b" --- prop> verifyNot checkMkdirDashPM "mkdir_func -pm 0755 a/b" --- prop> verifyNot checkMkdirDashPM "mkdir -p -m 0755 singlelevel" --- prop> verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../bin" --- prop> verify checkMkdirDashPM "mkdir -p -m 0755 ../bin/laden" --- prop> verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./bin" --- prop> verify checkMkdirDashPM "mkdir -p -m 0755 ./bin/laden" --- prop> verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./../bin" --- prop> verifyNot checkMkdirDashPM "mkdir -p -m 0755 .././bin" --- prop> verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../../bin" +-- >>> prop $ verify checkMkdirDashPM "mkdir -p -m 0755 a/b" +-- >>> prop $ verify checkMkdirDashPM "mkdir -pm 0755 $dir" +-- >>> prop $ verify checkMkdirDashPM "mkdir -vpm 0755 a/b" +-- >>> prop $ verify checkMkdirDashPM "mkdir -pm 0755 -v a/b" +-- >>> prop $ verify checkMkdirDashPM "mkdir --parents --mode=0755 a/b" +-- >>> prop $ verify checkMkdirDashPM "mkdir --parents --mode 0755 a/b" +-- >>> prop $ verify checkMkdirDashPM "mkdir -p --mode=0755 a/b" +-- >>> prop $ verify checkMkdirDashPM "mkdir --parents -m 0755 a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -m 0755 a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir --parents a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir --mode=0755 a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir_func -pm 0755 a/b" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 singlelevel" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../bin" +-- >>> prop $ verify checkMkdirDashPM "mkdir -p -m 0755 ../bin/laden" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./bin" +-- >>> prop $ verify checkMkdirDashPM "mkdir -p -m 0755 ./bin/laden" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./../bin" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 .././bin" +-- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../../bin" checkMkdirDashPM = CommandCheck (Basename "mkdir") check where check t = potentially $ do @@ -428,13 +427,13 @@ checkMkdirDashPM = CommandCheck (Basename "mkdir") check -- | --- prop> verify checkNonportableSignals "trap f 8" --- prop> verifyNot checkNonportableSignals "trap f 0" --- prop> verifyNot checkNonportableSignals "trap f 14" --- prop> verify checkNonportableSignals "trap f SIGKILL" --- prop> verify checkNonportableSignals "trap f 9" --- prop> verify checkNonportableSignals "trap f stop" --- prop> verifyNot checkNonportableSignals "trap 'stop' int" +-- >>> prop $ verify checkNonportableSignals "trap f 8" +-- >>> prop $ verifyNot checkNonportableSignals "trap f 0" +-- >>> prop $ verifyNot checkNonportableSignals "trap f 14" +-- >>> prop $ verify checkNonportableSignals "trap f SIGKILL" +-- >>> prop $ verify checkNonportableSignals "trap f 9" +-- >>> prop $ verify checkNonportableSignals "trap f stop" +-- >>> prop $ verifyNot checkNonportableSignals "trap 'stop' int" checkNonportableSignals = CommandCheck (Exactly "trap") (f . arguments) where f args = case args of @@ -464,10 +463,10 @@ checkNonportableSignals = CommandCheck (Exactly "trap") (f . arguments) -- | --- prop> verify checkInteractiveSu "su; rm file; su $USER" --- prop> verify checkInteractiveSu "su foo; something; exit" --- prop> verifyNot checkInteractiveSu "echo rm | su foo" --- prop> verifyNot checkInteractiveSu "su root < script" +-- >>> prop $ verify checkInteractiveSu "su; rm file; su $USER" +-- >>> prop $ verify checkInteractiveSu "su foo; something; exit" +-- >>> prop $ verifyNot checkInteractiveSu "echo rm | su foo" +-- >>> prop $ verifyNot checkInteractiveSu "su root < script" checkInteractiveSu = CommandCheck (Basename "su") f where f cmd = when (length (arguments cmd) <= 1) $ do @@ -485,10 +484,10 @@ checkInteractiveSu = CommandCheck (Basename "su") f -- | -- This is hard to get right without properly parsing ssh args -- --- prop> verify checkSshCommandString "ssh host \"echo $PS1\"" --- prop> verifyNot checkSshCommandString "ssh host \"ls foo\"" --- prop> verifyNot checkSshCommandString "ssh \"$host\"" --- prop> verifyNot checkSshCommandString "ssh -i key \"$host\"" +-- >>> prop $ verify checkSshCommandString "ssh host \"echo $PS1\"" +-- >>> prop $ verifyNot checkSshCommandString "ssh host \"ls foo\"" +-- >>> prop $ verifyNot checkSshCommandString "ssh \"$host\"" +-- >>> prop $ verifyNot checkSshCommandString "ssh -i key \"$host\"" checkSshCommandString = CommandCheck (Basename "ssh") (f . arguments) where isOption x = "-" `isPrefixOf` (concat $ oversimplify x) @@ -505,24 +504,24 @@ checkSshCommandString = CommandCheck (Basename "ssh") (f . arguments) -- | --- prop> verify checkPrintfVar "printf \"Lol: $s\"" --- prop> verifyNot checkPrintfVar "printf 'Lol: $s'" --- prop> verify checkPrintfVar "printf -v cow $(cmd)" --- prop> verifyNot checkPrintfVar "printf \"%${count}s\" var" --- prop> verify checkPrintfVar "printf '%s %s %s' foo bar" --- prop> verify checkPrintfVar "printf foo bar baz" --- prop> verify checkPrintfVar "printf -- foo bar baz" --- prop> verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\"" --- prop> verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png" --- prop> verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz" --- prop> verifyNot checkPrintfVar "printf '%(%s%s)T' -1" --- prop> verify checkPrintfVar "printf '%s %s\\n' 1 2 3" --- prop> verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4" --- prop> verify checkPrintfVar "printf '%*s\\n' 1" --- prop> verifyNot checkPrintfVar "printf '%*s\\n' 1 2" --- prop> verifyNot checkPrintfVar "printf $'string'" --- prop> verify checkPrintfVar "printf '%-*s\\n' 1" --- prop> verifyNot checkPrintfVar "printf '%-*s\\n' 1 2" +-- >>> prop $ verify checkPrintfVar "printf \"Lol: $s\"" +-- >>> prop $ verifyNot checkPrintfVar "printf 'Lol: $s'" +-- >>> prop $ verify checkPrintfVar "printf -v cow $(cmd)" +-- >>> prop $ verifyNot checkPrintfVar "printf \"%${count}s\" var" +-- >>> prop $ verify checkPrintfVar "printf '%s %s %s' foo bar" +-- >>> prop $ verify checkPrintfVar "printf foo bar baz" +-- >>> prop $ verify checkPrintfVar "printf -- foo bar baz" +-- >>> prop $ verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\"" +-- >>> prop $ verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png" +-- >>> prop $ verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz" +-- >>> prop $ verifyNot checkPrintfVar "printf '%(%s%s)T' -1" +-- >>> prop $ verify checkPrintfVar "printf '%s %s\\n' 1 2 3" +-- >>> prop $ verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4" +-- >>> prop $ verify checkPrintfVar "printf '%*s\\n' 1" +-- >>> prop $ verifyNot checkPrintfVar "printf '%*s\\n' 1 2" +-- >>> prop $ verifyNot checkPrintfVar "printf $'string'" +-- >>> prop $ verify checkPrintfVar "printf '%-*s\\n' 1" +-- >>> prop $ verifyNot checkPrintfVar "printf '%-*s\\n' 1 2" checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where f (doubledash:rest) | getLiteralString doubledash == Just "--" = f rest f (dashv:var:rest) | getLiteralString dashv == Just "-v" = f rest @@ -572,12 +571,12 @@ checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where -- | --- prop> verify checkUuoeCmd "echo $(date)" --- prop> verify checkUuoeCmd "echo `date`" --- prop> verify checkUuoeCmd "echo \"$(date)\"" --- prop> verify checkUuoeCmd "echo \"`date`\"" --- prop> verifyNot checkUuoeCmd "echo \"The time is $(date)\"" --- prop> verifyNot checkUuoeCmd "echo \"$(>> prop $ verify checkUuoeCmd "echo $(date)" +-- >>> prop $ verify checkUuoeCmd "echo `date`" +-- >>> prop $ verify checkUuoeCmd "echo \"$(date)\"" +-- >>> prop $ verify checkUuoeCmd "echo \"`date`\"" +-- >>> prop $ verifyNot checkUuoeCmd "echo \"The time is $(date)\"" +-- >>> prop $ verifyNot checkUuoeCmd "echo \"$( verify checkSetAssignment "set foo 42" --- prop> verify checkSetAssignment "set foo = 42" --- prop> verify checkSetAssignment "set foo=42" --- prop> verifyNot checkSetAssignment "set -- if=/dev/null" --- prop> verifyNot checkSetAssignment "set 'a=5'" --- prop> verifyNot checkSetAssignment "set" +-- >>> prop $ verify checkSetAssignment "set foo 42" +-- >>> prop $ verify checkSetAssignment "set foo = 42" +-- >>> prop $ verify checkSetAssignment "set foo=42" +-- >>> prop $ verifyNot checkSetAssignment "set -- if=/dev/null" +-- >>> prop $ verifyNot checkSetAssignment "set 'a=5'" +-- >>> prop $ verifyNot checkSetAssignment "set" checkSetAssignment = CommandCheck (Exactly "set") (f . arguments) where f (var:value:rest) = @@ -611,10 +610,10 @@ checkSetAssignment = CommandCheck (Exactly "set") (f . arguments) -- | --- prop> verify checkExportedExpansions "export $foo" --- prop> verify checkExportedExpansions "export \"$foo\"" --- prop> verifyNot checkExportedExpansions "export foo" --- prop> verifyNot checkExportedExpansions "export ${foo?}" +-- >>> prop $ verify checkExportedExpansions "export $foo" +-- >>> prop $ verify checkExportedExpansions "export \"$foo\"" +-- >>> prop $ verifyNot checkExportedExpansions "export foo" +-- >>> prop $ verifyNot checkExportedExpansions "export ${foo?}" checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . arguments) where check t = potentially $ do @@ -624,14 +623,14 @@ checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . argumen "This does not export '" ++ name ++ "'. Remove $/${} for that, or use ${var?} to quiet." -- | --- prop> verify checkReadExpansions "read $var" --- prop> verify checkReadExpansions "read -r $var" --- prop> verifyNot checkReadExpansions "read -p $var" --- prop> verifyNot checkReadExpansions "read -rd $delim name" --- prop> verify checkReadExpansions "read \"$var\"" --- prop> verify checkReadExpansions "read -a $var" --- prop> verifyNot checkReadExpansions "read $1" --- prop> verifyNot checkReadExpansions "read ${var?}" +-- >>> prop $ verify checkReadExpansions "read $var" +-- >>> prop $ verify checkReadExpansions "read -r $var" +-- >>> prop $ verifyNot checkReadExpansions "read -p $var" +-- >>> prop $ verifyNot checkReadExpansions "read -rd $delim name" +-- >>> prop $ verify checkReadExpansions "read \"$var\"" +-- >>> prop $ verify checkReadExpansions "read -a $var" +-- >>> prop $ verifyNot checkReadExpansions "read $1" +-- >>> prop $ verifyNot checkReadExpansions "read ${var?}" checkReadExpansions = CommandCheck (Exactly "read") check where options = getGnuOpts "sreu:n:N:i:p:a:" @@ -659,9 +658,9 @@ getSingleUnmodifiedVariable word = _ -> Nothing -- | --- prop> verify checkAliasesUsesArgs "alias a='cp $1 /a'" --- prop> verifyNot checkAliasesUsesArgs "alias $1='foo'" --- prop> verify checkAliasesUsesArgs "alias a=\"echo \\${@}\"" +-- >>> prop $ verify checkAliasesUsesArgs "alias a='cp $1 /a'" +-- >>> prop $ verifyNot checkAliasesUsesArgs "alias $1='foo'" +-- >>> prop $ verify checkAliasesUsesArgs "alias a=\"echo \\${@}\"" checkAliasesUsesArgs = CommandCheck (Exactly "alias") (f . arguments) where re = mkRegex "\\$\\{?[0-9*@]" @@ -674,9 +673,9 @@ checkAliasesUsesArgs = CommandCheck (Exactly "alias") (f . arguments) -- | --- prop> verify checkAliasesExpandEarly "alias foo=\"echo $PWD\"" --- prop> verifyNot checkAliasesExpandEarly "alias -p" --- prop> verifyNot checkAliasesExpandEarly "alias foo='echo {1..10}'" +-- >>> prop $ verify checkAliasesExpandEarly "alias foo=\"echo $PWD\"" +-- >>> prop $ verifyNot checkAliasesExpandEarly "alias -p" +-- >>> prop $ verifyNot checkAliasesExpandEarly "alias foo='echo {1..10}'" checkAliasesExpandEarly = CommandCheck (Exactly "alias") (f . arguments) where f = mapM_ checkArg @@ -686,8 +685,8 @@ checkAliasesExpandEarly = CommandCheck (Exactly "alias") (f . arguments) checkArg _ = return () --- prop> verify checkUnsetGlobs "unset foo[1]" --- prop> verifyNot checkUnsetGlobs "unset foo" +-- >>> prop $ verify checkUnsetGlobs "unset foo[1]" +-- >>> prop $ verifyNot checkUnsetGlobs "unset foo" checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments) where check arg = @@ -696,14 +695,14 @@ checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments) -- | --- prop> verify checkFindWithoutPath "find -type f" --- prop> verify checkFindWithoutPath "find" --- prop> verifyNot checkFindWithoutPath "find . -type f" --- prop> verifyNot checkFindWithoutPath "find -H -L \"$path\" -print" --- prop> verifyNot checkFindWithoutPath "find -O3 ." --- prop> verifyNot checkFindWithoutPath "find -D exec ." --- prop> verifyNot checkFindWithoutPath "find --help" --- prop> verifyNot checkFindWithoutPath "find -Hx . -print" +-- >>> prop $ verify checkFindWithoutPath "find -type f" +-- >>> prop $ verify checkFindWithoutPath "find" +-- >>> prop $ verifyNot checkFindWithoutPath "find . -type f" +-- >>> prop $ verifyNot checkFindWithoutPath "find -H -L \"$path\" -print" +-- >>> prop $ verifyNot checkFindWithoutPath "find -O3 ." +-- >>> prop $ verifyNot checkFindWithoutPath "find -D exec ." +-- >>> prop $ verifyNot checkFindWithoutPath "find --help" +-- >>> prop $ verifyNot checkFindWithoutPath "find -Hx . -print" checkFindWithoutPath = CommandCheck (Basename "find") f where f t@(T_SimpleCommand _ _ (cmd:args)) = @@ -723,10 +722,10 @@ checkFindWithoutPath = CommandCheck (Basename "find") f -- | --- prop> verify checkTimeParameters "time -f lol sleep 10" --- prop> verifyNot checkTimeParameters "time sleep 10" --- prop> verifyNot checkTimeParameters "time -p foo" --- prop> verifyNot checkTimeParameters "command time -f lol sleep 10" +-- >>> prop $ verify checkTimeParameters "time -f lol sleep 10" +-- >>> prop $ verifyNot checkTimeParameters "time sleep 10" +-- >>> prop $ verifyNot checkTimeParameters "time -p foo" +-- >>> prop $ verifyNot checkTimeParameters "command time -f lol sleep 10" checkTimeParameters = CommandCheck (Exactly "time") f where f (T_SimpleCommand _ _ (cmd:args:_)) = @@ -738,9 +737,9 @@ checkTimeParameters = CommandCheck (Exactly "time") f f _ = return () -- | --- prop> verify checkTimedCommand "#!/bin/sh\ntime -p foo | bar" --- prop> verify checkTimedCommand "#!/bin/dash\ntime ( foo; bar; )" --- prop> verifyNot checkTimedCommand "#!/bin/sh\ntime sleep 1" +-- >>> prop $ verify checkTimedCommand "#!/bin/sh\ntime -p foo | bar" +-- >>> prop $ verify checkTimedCommand "#!/bin/dash\ntime ( foo; bar; )" +-- >>> prop $ verifyNot checkTimedCommand "#!/bin/sh\ntime sleep 1" checkTimedCommand = CommandCheck (Exactly "time") f where f (T_SimpleCommand _ _ (c:args@(_:_))) = whenShell [Sh, Dash] $ do @@ -765,8 +764,8 @@ checkTimedCommand = CommandCheck (Exactly "time") f where _ -> return False -- | --- prop> verify checkLocalScope "local foo=3" --- prop> verifyNot checkLocalScope "f() { local foo=3; }" +-- >>> prop $ verify checkLocalScope "local foo=3" +-- >>> prop $ verifyNot checkLocalScope "f() { local foo=3; }" checkLocalScope = CommandCheck (Exactly "local") $ \t -> whenShell [Bash, Dash] $ do -- Ksh allows it, Sh doesn't support local path <- getPathM t @@ -774,27 +773,27 @@ checkLocalScope = CommandCheck (Exactly "local") $ \t -> err (getId $ getCommandTokenOrThis t) 2168 "'local' is only valid in functions." -- | --- prop> verify checkDeprecatedTempfile "var=$(tempfile)" --- prop> verifyNot checkDeprecatedTempfile "tempfile=$(mktemp)" +-- >>> prop $ verify checkDeprecatedTempfile "var=$(tempfile)" +-- >>> prop $ verifyNot checkDeprecatedTempfile "tempfile=$(mktemp)" checkDeprecatedTempfile = CommandCheck (Basename "tempfile") $ \t -> warn (getId $ getCommandTokenOrThis t) 2186 "tempfile is deprecated. Use mktemp instead." -- | --- prop> verify checkDeprecatedEgrep "egrep '.+'" +-- >>> prop $ verify checkDeprecatedEgrep "egrep '.+'" checkDeprecatedEgrep = CommandCheck (Basename "egrep") $ \t -> info (getId $ getCommandTokenOrThis t) 2196 "egrep is non-standard and deprecated. Use grep -E instead." -- | --- prop> verify checkDeprecatedFgrep "fgrep '*' files" +-- >>> prop $ verify checkDeprecatedFgrep "fgrep '*' files" checkDeprecatedFgrep = CommandCheck (Basename "fgrep") $ \t -> info (getId $ getCommandTokenOrThis t) 2197 "fgrep is non-standard and deprecated. Use grep -F instead." -- | --- prop> verify checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; esac; done" --- prop> verify checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done" --- prop> verifyNot checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done" --- prop> verifyNot checkWhileGetoptsCase "while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done" --- prop> verifyNot checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done" +-- >>> prop $ verify checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; esac; done" +-- >>> prop $ verify checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done" +-- >>> prop $ verifyNot checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done" +-- >>> prop $ verifyNot checkWhileGetoptsCase "while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done" +-- >>> prop $ verifyNot checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done" checkWhileGetoptsCase = CommandCheck (Exactly "getopts") f where f :: Token -> Analysis @@ -860,19 +859,19 @@ checkWhileGetoptsCase = CommandCheck (Exactly "getopts") f _ -> Nothing -- | --- prop> verify checkCatastrophicRm "rm -r $1/$2" --- prop> verify checkCatastrophicRm "rm -r /home/$foo" --- prop> verifyNot checkCatastrophicRm "rm -r /home/${USER:?}/*" --- prop> verify checkCatastrophicRm "rm -fr /home/$(whoami)/*" --- prop> verifyNot checkCatastrophicRm "rm -r /home/${USER:-thing}/*" --- prop> verify checkCatastrophicRm "rm --recursive /etc/*$config*" --- prop> verify checkCatastrophicRm "rm -rf /home" --- prop> verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}" --- prop> verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec" --- prop> verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbin}}/$exec" --- prop> verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}/$exec" --- prop> verify checkCatastrophicRm "rm -rf /usr /lib/nvidia-current/xorg/xorg" --- prop> verify checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*" +-- >>> prop $ verify checkCatastrophicRm "rm -r $1/$2" +-- >>> prop $ verify checkCatastrophicRm "rm -r /home/$foo" +-- >>> prop $ verifyNot checkCatastrophicRm "rm -r /home/${USER:?}/*" +-- >>> prop $ verify checkCatastrophicRm "rm -fr /home/$(whoami)/*" +-- >>> prop $ verifyNot checkCatastrophicRm "rm -r /home/${USER:-thing}/*" +-- >>> prop $ verify checkCatastrophicRm "rm --recursive /etc/*$config*" +-- >>> prop $ verify checkCatastrophicRm "rm -rf /home" +-- >>> prop $ verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}" +-- >>> prop $ verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec" +-- >>> prop $ verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbin}}/$exec" +-- >>> prop $ verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}/$exec" +-- >>> prop $ verify checkCatastrophicRm "rm -rf /usr /lib/nvidia-current/xorg/xorg" +-- >>> prop $ verify checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*" checkCatastrophicRm = CommandCheck (Basename "rm") $ \t -> when (isRecursive t) $ mapM_ (mapM_ checkWord . braceExpand) $ arguments t @@ -922,8 +921,8 @@ checkCatastrophicRm = CommandCheck (Basename "rm") $ \t -> -- | --- prop> verify checkLetUsage "let a=1" --- prop> verifyNot checkLetUsage "(( a=1 ))" +-- >>> prop $ verify checkLetUsage "let a=1" +-- >>> prop $ verifyNot checkLetUsage "(( a=1 ))" checkLetUsage = CommandCheck (Exactly "let") f where f t = whenShell [Bash,Ksh] $ do @@ -944,15 +943,15 @@ missingDestination handler token = do map snd args -- | --- prop> verify checkMvArguments "mv 'foo bar'" --- prop> verifyNot checkMvArguments "mv foo bar" --- prop> verifyNot checkMvArguments "mv 'foo bar'{,bak}" --- prop> verifyNot checkMvArguments "mv \"$@\"" --- prop> verifyNot checkMvArguments "mv -t foo bar" --- prop> verifyNot checkMvArguments "mv --target-directory=foo bar" --- prop> verifyNot checkMvArguments "mv --target-direc=foo bar" --- prop> verifyNot checkMvArguments "mv --version" --- prop> verifyNot checkMvArguments "mv \"${!var}\"" +-- >>> prop $ verify checkMvArguments "mv 'foo bar'" +-- >>> prop $ verifyNot checkMvArguments "mv foo bar" +-- >>> prop $ verifyNot checkMvArguments "mv 'foo bar'{,bak}" +-- >>> prop $ verifyNot checkMvArguments "mv \"$@\"" +-- >>> prop $ verifyNot checkMvArguments "mv -t foo bar" +-- >>> prop $ verifyNot checkMvArguments "mv --target-directory=foo bar" +-- >>> prop $ verifyNot checkMvArguments "mv --target-direc=foo bar" +-- >>> prop $ verifyNot checkMvArguments "mv --version" +-- >>> prop $ verifyNot checkMvArguments "mv \"${!var}\"" checkMvArguments = CommandCheck (Basename "mv") $ missingDestination f where f t = err (getId t) 2224 "This mv has no destination. Check the arguments." @@ -967,9 +966,9 @@ checkLnArguments = CommandCheck (Basename "ln") $ missingDestination f -- | --- prop> verify checkFindRedirections "find . -exec echo {} > file \\;" --- prop> verifyNot checkFindRedirections "find . -exec echo {} \\; > file" --- prop> verifyNot checkFindRedirections "find . -execdir sh -c 'foo > file' \\;" +-- >>> prop $ verify checkFindRedirections "find . -exec echo {} > file \\;" +-- >>> prop $ verifyNot checkFindRedirections "find . -exec echo {} \\; > file" +-- >>> prop $ verifyNot checkFindRedirections "find . -execdir sh -c 'foo > file' \\;" checkFindRedirections = CommandCheck (Basename "find") f where f t = do @@ -984,18 +983,18 @@ checkFindRedirections = CommandCheck (Basename "find") f "Redirection applies to the find command itself. Rewrite to work per action (or move to end)." _ -> return () --- prop> verify checkWhich "which '.+'" +-- >>> prop $ verify checkWhich "which '.+'" checkWhich = CommandCheck (Basename "which") $ \t -> info (getId $ getCommandTokenOrThis t) 2230 "which is non-standard. Use builtin 'command -v' instead." -- | --- prop> verify checkSudoRedirect "sudo echo 3 > /proc/file" --- prop> verify checkSudoRedirect "sudo cmd < input" --- prop> verify checkSudoRedirect "sudo cmd >> file" --- prop> verify checkSudoRedirect "sudo cmd &> file" --- prop> verifyNot checkSudoRedirect "sudo cmd 2>&1" --- prop> verifyNot checkSudoRedirect "sudo cmd 2> log" --- prop> verifyNot checkSudoRedirect "sudo cmd > /dev/null 2>&1" +-- >>> prop $ verify checkSudoRedirect "sudo echo 3 > /proc/file" +-- >>> prop $ verify checkSudoRedirect "sudo cmd < input" +-- >>> prop $ verify checkSudoRedirect "sudo cmd >> file" +-- >>> prop $ verify checkSudoRedirect "sudo cmd &> file" +-- >>> prop $ verifyNot checkSudoRedirect "sudo cmd 2>&1" +-- >>> prop $ verifyNot checkSudoRedirect "sudo cmd 2> log" +-- >>> prop $ verifyNot checkSudoRedirect "sudo cmd > /dev/null 2>&1" checkSudoRedirect = CommandCheck (Basename "sudo") f where f t = do @@ -1020,13 +1019,13 @@ checkSudoRedirect = CommandCheck (Basename "sudo") f special file = concat (oversimplify file) == "/dev/null" -- | --- prop> verify checkSudoArgs "sudo cd /root" --- prop> verify checkSudoArgs "sudo export x=3" --- prop> verifyNot checkSudoArgs "sudo ls /usr/local/protected" --- prop> verifyNot checkSudoArgs "sudo ls && export x=3" --- prop> verifyNot checkSudoArgs "sudo echo ls" --- prop> verifyNot checkSudoArgs "sudo -n -u export ls" --- prop> verifyNot checkSudoArgs "sudo docker export foo" +-- >>> prop $ verify checkSudoArgs "sudo cd /root" +-- >>> prop $ verify checkSudoArgs "sudo export x=3" +-- >>> prop $ verifyNot checkSudoArgs "sudo ls /usr/local/protected" +-- >>> prop $ verifyNot checkSudoArgs "sudo ls && export x=3" +-- >>> prop $ verifyNot checkSudoArgs "sudo echo ls" +-- >>> prop $ verifyNot checkSudoArgs "sudo -n -u export ls" +-- >>> prop $ verifyNot checkSudoArgs "sudo docker export foo" checkSudoArgs = CommandCheck (Basename "sudo") f where f t = potentially $ do @@ -1039,4 +1038,3 @@ checkSudoArgs = CommandCheck (Basename "sudo") f builtins = [ "cd", "eval", "export", "history", "read", "source", "wait" ] -- This mess is why ShellCheck prefers not to know. parseOpts = getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:" - diff --git a/src/ShellCheck/Checks/ShellSupport.hs b/src/ShellCheck/Checks/ShellSupport.hs index 1196f26..6e4dd54 100644 --- a/src/ShellCheck/Checks/ShellSupport.hs +++ b/src/ShellCheck/Checks/ShellSupport.hs @@ -65,9 +65,9 @@ verify c s = producesComments (testChecker c) s == Just True verifyNot c s = producesComments (testChecker c) s == Just False -- | --- prop> verify checkForDecimals "((3.14*c))" --- prop> verify checkForDecimals "foo[1.2]=bar" --- prop> verifyNot checkForDecimals "declare -A foo; foo[1.2]=bar" +-- >>> prop $ verify checkForDecimals "((3.14*c))" +-- >>> prop $ verify checkForDecimals "foo[1.2]=bar" +-- >>> prop $ verifyNot checkForDecimals "declare -A foo; foo[1.2]=bar" checkForDecimals = ForShell [Sh, Dash, Bash] f where f t@(TA_Expansion id _) = potentially $ do @@ -79,62 +79,62 @@ checkForDecimals = ForShell [Sh, Dash, Bash] f -- | --- prop> verify checkBashisms "while read a; do :; done < <(a)" --- prop> verify checkBashisms "[ foo -nt bar ]" --- prop> verify checkBashisms "echo $((i++))" --- prop> verify checkBashisms "rm !(*.hs)" --- prop> verify checkBashisms "source file" --- prop> verify checkBashisms "[ \"$a\" == 42 ]" --- prop> verify checkBashisms "echo ${var[1]}" --- prop> verify checkBashisms "echo ${!var[@]}" --- prop> verify checkBashisms "echo ${!var*}" --- prop> verify checkBashisms "echo ${var:4:12}" --- prop> verifyNot checkBashisms "echo ${var:-4}" --- prop> verify checkBashisms "echo ${var//foo/bar}" --- prop> verify checkBashisms "exec -c env" --- prop> verify checkBashisms "echo -n \"Foo: \"" --- prop> verify checkBashisms "let n++" --- prop> verify checkBashisms "echo $RANDOM" --- prop> verify checkBashisms "echo $((RANDOM%6+1))" --- prop> verify checkBashisms "foo &> /dev/null" --- prop> verify checkBashisms "foo > file*.txt" --- prop> verify checkBashisms "read -ra foo" --- prop> verify checkBashisms "[ -a foo ]" --- prop> verifyNot checkBashisms "[ foo -a bar ]" --- prop> verify checkBashisms "trap mything ERR INT" --- prop> verifyNot checkBashisms "trap mything INT TERM" --- prop> verify checkBashisms "cat < /dev/tcp/host/123" --- prop> verify checkBashisms "trap mything ERR SIGTERM" --- prop> verify checkBashisms "echo *[^0-9]*" --- prop> verify checkBashisms "exec {n}>&2" --- prop> verify checkBashisms "echo ${!var}" --- prop> verify checkBashisms "printf -v '%s' \"$1\"" --- prop> verify checkBashisms "printf '%q' \"$1\"" --- prop> verifyNot checkBashisms "#!/bin/dash\n[ foo -nt bar ]" --- prop> verify checkBashisms "#!/bin/sh\necho -n foo" --- prop> verifyNot checkBashisms "#!/bin/dash\necho -n foo" --- prop> verifyNot checkBashisms "#!/bin/dash\nlocal foo" --- prop> verifyNot checkBashisms "#!/bin/dash\nread -p foo -r bar" --- prop> verifyNot checkBashisms "HOSTNAME=foo; echo $HOSTNAME" --- prop> verify checkBashisms "RANDOM=9; echo $RANDOM" --- prop> verify checkBashisms "foo-bar() { true; }" --- prop> verify checkBashisms "echo $( verify checkBashisms "echo ` verify checkBashisms "trap foo int" --- prop> verify checkBashisms "trap foo sigint" --- prop> verifyNot checkBashisms "#!/bin/dash\ntrap foo int" --- prop> verifyNot checkBashisms "#!/bin/dash\ntrap foo INT" --- prop> verify checkBashisms "#!/bin/dash\ntrap foo SIGINT" --- prop> verify checkBashisms "#!/bin/dash\necho foo 42>/dev/null" --- prop> verifyNot checkBashisms "#!/bin/sh\necho $LINENO" --- prop> verify checkBashisms "#!/bin/dash\necho $MACHTYPE" --- prop> verify checkBashisms "#!/bin/sh\ncmd >& file" --- prop> verifyNot checkBashisms "#!/bin/sh\ncmd 2>&1" --- prop> verifyNot checkBashisms "#!/bin/sh\ncmd >&2" --- prop> verifyNot checkBashisms "#!/bin/sh\nprintf -- -f\n" --- prop> verify checkBashisms "#!/bin/sh\nfoo+=bar" --- prop> verify checkBashisms "#!/bin/sh\necho ${@%foo}" --- prop> verifyNot checkBashisms "#!/bin/sh\necho ${##}" +-- >>> prop $ verify checkBashisms "while read a; do :; done < <(a)" +-- >>> prop $ verify checkBashisms "[ foo -nt bar ]" +-- >>> prop $ verify checkBashisms "echo $((i++))" +-- >>> prop $ verify checkBashisms "rm !(*.hs)" +-- >>> prop $ verify checkBashisms "source file" +-- >>> prop $ verify checkBashisms "[ \"$a\" == 42 ]" +-- >>> prop $ verify checkBashisms "echo ${var[1]}" +-- >>> prop $ verify checkBashisms "echo ${!var[@]}" +-- >>> prop $ verify checkBashisms "echo ${!var*}" +-- >>> prop $ verify checkBashisms "echo ${var:4:12}" +-- >>> prop $ verifyNot checkBashisms "echo ${var:-4}" +-- >>> prop $ verify checkBashisms "echo ${var//foo/bar}" +-- >>> prop $ verify checkBashisms "exec -c env" +-- >>> prop $ verify checkBashisms "echo -n \"Foo: \"" +-- >>> prop $ verify checkBashisms "let n++" +-- >>> prop $ verify checkBashisms "echo $RANDOM" +-- >>> prop $ verify checkBashisms "echo $((RANDOM%6+1))" +-- >>> prop $ verify checkBashisms "foo &> /dev/null" +-- >>> prop $ verify checkBashisms "foo > file*.txt" +-- >>> prop $ verify checkBashisms "read -ra foo" +-- >>> prop $ verify checkBashisms "[ -a foo ]" +-- >>> prop $ verifyNot checkBashisms "[ foo -a bar ]" +-- >>> prop $ verify checkBashisms "trap mything ERR INT" +-- >>> prop $ verifyNot checkBashisms "trap mything INT TERM" +-- >>> prop $ verify checkBashisms "cat < /dev/tcp/host/123" +-- >>> prop $ verify checkBashisms "trap mything ERR SIGTERM" +-- >>> prop $ verify checkBashisms "echo *[^0-9]*" +-- >>> prop $ verify checkBashisms "exec {n}>&2" +-- >>> prop $ verify checkBashisms "echo ${!var}" +-- >>> prop $ verify checkBashisms "printf -v '%s' \"$1\"" +-- >>> prop $ verify checkBashisms "printf '%q' \"$1\"" +-- >>> prop $ verifyNot checkBashisms "#!/bin/dash\n[ foo -nt bar ]" +-- >>> prop $ verify checkBashisms "#!/bin/sh\necho -n foo" +-- >>> prop $ verifyNot checkBashisms "#!/bin/dash\necho -n foo" +-- >>> prop $ verifyNot checkBashisms "#!/bin/dash\nlocal foo" +-- >>> prop $ verifyNot checkBashisms "#!/bin/dash\nread -p foo -r bar" +-- >>> prop $ verifyNot checkBashisms "HOSTNAME=foo; echo $HOSTNAME" +-- >>> prop $ verify checkBashisms "RANDOM=9; echo $RANDOM" +-- >>> prop $ verify checkBashisms "foo-bar() { true; }" +-- >>> prop $ verify checkBashisms "echo $(>> prop $ verify checkBashisms "echo `>> prop $ verify checkBashisms "trap foo int" +-- >>> prop $ verify checkBashisms "trap foo sigint" +-- >>> prop $ verifyNot checkBashisms "#!/bin/dash\ntrap foo int" +-- >>> prop $ verifyNot checkBashisms "#!/bin/dash\ntrap foo INT" +-- >>> prop $ verify checkBashisms "#!/bin/dash\ntrap foo SIGINT" +-- >>> prop $ verify checkBashisms "#!/bin/dash\necho foo 42>/dev/null" +-- >>> prop $ verifyNot checkBashisms "#!/bin/sh\necho $LINENO" +-- >>> prop $ verify checkBashisms "#!/bin/dash\necho $MACHTYPE" +-- >>> prop $ verify checkBashisms "#!/bin/sh\ncmd >& file" +-- >>> prop $ verifyNot checkBashisms "#!/bin/sh\ncmd 2>&1" +-- >>> prop $ verifyNot checkBashisms "#!/bin/sh\ncmd >&2" +-- >>> prop $ verifyNot checkBashisms "#!/bin/sh\nprintf -- -f\n" +-- >>> prop $ verify checkBashisms "#!/bin/sh\nfoo+=bar" +-- >>> prop $ verify checkBashisms "#!/bin/sh\necho ${@%foo}" +-- >>> prop $ verifyNot checkBashisms "#!/bin/sh\necho ${##}" checkBashisms = ForShell [Sh, Dash] $ \t -> do params <- ask kludge params t @@ -317,8 +317,8 @@ checkBashisms = ForShell [Sh, Dash] $ \t -> do _ -> False -- | --- prop> verify checkEchoSed "FOO=$(echo \"$cow\" | sed 's/foo/bar/g')" --- prop> verify checkEchoSed "rm $(echo $cow | sed -e 's,foo,bar,')" +-- >>> prop $ verify checkEchoSed "FOO=$(echo \"$cow\" | sed 's/foo/bar/g')" +-- >>> prop $ verify checkEchoSed "rm $(echo $cow | sed -e 's,foo,bar,')" checkEchoSed = ForShell [Bash, Ksh] f where f (T_Pipeline id _ [a, b]) = @@ -345,10 +345,10 @@ checkEchoSed = ForShell [Bash, Ksh] f -- | --- prop> verify checkBraceExpansionVars "echo {1..$n}" --- prop> verifyNot checkBraceExpansionVars "echo {1,3,$n}" --- prop> verify checkBraceExpansionVars "eval echo DSC{0001..$n}.jpg" --- prop> verify checkBraceExpansionVars "echo {$i..100}" +-- >>> prop $ verify checkBraceExpansionVars "echo {1..$n}" +-- >>> prop $ verifyNot checkBraceExpansionVars "echo {1,3,$n}" +-- >>> prop $ verify checkBraceExpansionVars "eval echo DSC{0001..$n}.jpg" +-- >>> prop $ verify checkBraceExpansionVars "echo {$i..100}" checkBraceExpansionVars = ForShell [Bash] f where f t@(T_BraceExpansion id list) = mapM_ check list @@ -374,12 +374,12 @@ checkBraceExpansionVars = ForShell [Bash] f -- | --- prop> verify checkMultiDimensionalArrays "foo[a][b]=3" --- prop> verifyNot checkMultiDimensionalArrays "foo[a]=3" --- prop> verify checkMultiDimensionalArrays "foo=( [a][b]=c )" --- prop> verifyNot checkMultiDimensionalArrays "foo=( [a]=c )" --- prop> verify checkMultiDimensionalArrays "echo ${foo[bar][baz]}" --- prop> verifyNot checkMultiDimensionalArrays "echo ${foo[bar]}" +-- >>> prop $ verify checkMultiDimensionalArrays "foo[a][b]=3" +-- >>> prop $ verifyNot checkMultiDimensionalArrays "foo[a]=3" +-- >>> prop $ verify checkMultiDimensionalArrays "foo=( [a][b]=c )" +-- >>> prop $ verifyNot checkMultiDimensionalArrays "foo=( [a]=c )" +-- >>> prop $ verify checkMultiDimensionalArrays "echo ${foo[bar][baz]}" +-- >>> prop $ verifyNot checkMultiDimensionalArrays "echo ${foo[bar]}" checkMultiDimensionalArrays = ForShell [Bash] f where f token = @@ -395,16 +395,16 @@ checkMultiDimensionalArrays = ForShell [Bash] f isMultiDim t = getBracedModifier (bracedString t) `matches` re -- | --- prop> verify checkPS1Assignments "PS1='\\033[1;35m\\$ '" --- prop> verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '" --- prop> verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" --- prop> verify checkPS1Assignments "PS1=$'\\x1b[c '" --- prop> verify checkPS1Assignments "PS1=$'\\e[3m; '" --- prop> verify checkPS1Assignments "export PS1=$'\\e[3m; '" --- prop> verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" --- prop> verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" --- prop> verifyNot checkPS1Assignments "PS1='e033x1B'" --- prop> verifyNot checkPS1Assignments "PS1='\\[\\e\\]'" +-- >>> prop $ verify checkPS1Assignments "PS1='\\033[1;35m\\$ '" +-- >>> prop $ verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '" +-- >>> prop $ verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" +-- >>> prop $ verify checkPS1Assignments "PS1=$'\\x1b[c '" +-- >>> prop $ verify checkPS1Assignments "PS1=$'\\e[3m; '" +-- >>> prop $ verify checkPS1Assignments "export PS1=$'\\e[3m; '" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='e033x1B'" +-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\e\\]'" checkPS1Assignments = ForShell [Bash] f where f token = case token of diff --git a/src/ShellCheck/Parser.hs b/src/ShellCheck/Parser.hs index efc39eb..69c8788 100644 --- a/src/ShellCheck/Parser.hs +++ b/src/ShellCheck/Parser.hs @@ -47,6 +47,10 @@ import qualified Control.Monad.Reader as Mr import qualified Control.Monad.State as Ms import qualified Data.Map as Map +prop :: Bool -> IO () +prop False = putStrLn "FAIL" +prop True = return () + type SCBase m = Mr.ReaderT (Environment m) (Ms.StateT SystemState m) type SCParser m v = ParsecT String UserState (SCBase m) v @@ -85,7 +89,7 @@ unicodeDoubleQuotes = "\x201C\x201D\x2033\x2036" unicodeSingleQuotes = "\x2018\x2019" -- | --- prop> isOk spacing " \\\n # Comment" +-- >>> prop $ isOk spacing " \\\n # Comment" spacing = do x <- many (many1 linewhitespace <|> try (string "\\\n" >> return "")) optional readComment @@ -97,9 +101,9 @@ spacing1 = do return spacing -- | --- prop> isOk allspacing "#foo" --- prop> isOk allspacing " #foo\n # bar\n#baz\n" --- prop> isOk allspacing "#foo\n#bar\n#baz\n" +-- >>> prop $ isOk allspacing "#foo" +-- >>> prop $ isOk allspacing " #foo\n # bar\n#baz\n" +-- >>> prop $ isOk allspacing "#foo\n#bar\n#baz\n" allspacing = do s <- spacing more <- option False (linefeed >> return True) @@ -673,29 +677,29 @@ readConditionContents single = -- | --- prop> isOk readArithmeticContents " n++ + ++c" --- prop> isOk readArithmeticContents "$N*4-(3,2)" --- prop> isOk readArithmeticContents "n|=2<<1" --- prop> isOk readArithmeticContents "n &= 2 **3" --- prop> isOk readArithmeticContents "1 |= 4 && n >>= 4" --- prop> isOk readArithmeticContents " 1 | 2 ||3|4" --- prop> isOk readArithmeticContents "3*2**10" --- prop> isOk readArithmeticContents "3" --- prop> isOk readArithmeticContents "a^!-b" --- prop> isOk readArithmeticContents "! $?" --- prop> isOk readArithmeticContents "10#08 * 16#f" --- prop> isOk readArithmeticContents "\"$((3+2))\" + '37'" --- prop> isOk readArithmeticContents "foo[9*y+x]++" --- prop> isOk readArithmeticContents "1+`echo 2`" --- prop> isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4" --- prop> isOk readArithmeticContents "$foo$bar" --- prop> isOk readArithmeticContents "i<(0+(1+1))" --- prop> isOk readArithmeticContents "a?b:c" --- prop> isOk readArithmeticContents "\\\n3 +\\\n 2" --- prop> isOk readArithmeticContents "a ? b ? c : d : e" --- prop> isOk readArithmeticContents "a ? b : c ? d : e" --- prop> isOk readArithmeticContents "!!a" --- prop> isOk readArithmeticContents "~0" +-- >>> prop $ isOk readArithmeticContents " n++ + ++c" +-- >>> prop $ isOk readArithmeticContents "$N*4-(3,2)" +-- >>> prop $ isOk readArithmeticContents "n|=2<<1" +-- >>> prop $ isOk readArithmeticContents "n &= 2 **3" +-- >>> prop $ isOk readArithmeticContents "1 |= 4 && n >>= 4" +-- >>> prop $ isOk readArithmeticContents " 1 | 2 ||3|4" +-- >>> prop $ isOk readArithmeticContents "3*2**10" +-- >>> prop $ isOk readArithmeticContents "3" +-- >>> prop $ isOk readArithmeticContents "a^!-b" +-- >>> prop $ isOk readArithmeticContents "! $?" +-- >>> prop $ isOk readArithmeticContents "10#08 * 16#f" +-- >>> prop $ isOk readArithmeticContents "\"$((3+2))\" + '37'" +-- >>> prop $ isOk readArithmeticContents "foo[9*y+x]++" +-- >>> prop $ isOk readArithmeticContents "1+`echo 2`" +-- >>> prop $ isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4" +-- >>> prop $ isOk readArithmeticContents "$foo$bar" +-- >>> prop $ isOk readArithmeticContents "i<(0+(1+1))" +-- >>> prop $ isOk readArithmeticContents "a?b:c" +-- >>> prop $ isOk readArithmeticContents "\\\n3 +\\\n 2" +-- >>> prop $ isOk readArithmeticContents "a ? b ? c : d : e" +-- >>> prop $ isOk readArithmeticContents "a ? b : c ? d : e" +-- >>> prop $ isOk readArithmeticContents "!!a" +-- >>> prop $ isOk readArithmeticContents "~0" readArithmeticContents :: Monad m => SCParser m Token readArithmeticContents = readSequence @@ -877,33 +881,33 @@ readArithmeticContents = -- | --- prop> isOk readCondition "[ \\( a = b \\) -a \\( c = d \\) ]" --- prop> isOk readCondition "[[ (a = b) || (c = d) ]]" --- prop> isOk readCondition "[[ $c = [[:alpha:].~-] ]]" --- prop> isOk readCondition "[[ $c =~ *foo* ]]" --- prop> isOk readCondition "[[ $c =~ f( ]] )* ]]" --- prop> isOk readCondition "[[ $c =~ a(b) ]]" --- prop> isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]" --- prop> isOk readCondition "[[ $c =~ ^[yY]$ ]]" --- prop> isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]" --- prop> isOk readCondition "[[ $l =~ ogg|flac ]]" --- prop> isOk readCondition "[ foo -a -f bar ]" --- prop> isOk readCondition "[[\na == b\n||\nc == d ]]" --- prop> isOk readCondition "[[\na == b ||\nc == d ]]" --- prop> isOk readCondition "[[ a == b\n||\nc == d ]]" --- prop> isOk readCondition "[[ a == b ||\n c == d ]]" --- prop> isWarning readCondition "[ a == b \n -o c == d ]" --- prop> isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]" --- prop> isOk readCondition "[ foo '>' bar ]" --- prop> isOk readCondition "[ foo \">=\" bar ]" --- prop> isOk readCondition "[ foo \\< bar ]" --- prop> isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]" --- prop> isOk readCondition "[ ]" --- prop> isOk readCondition "[ '(' x \")\" ]" --- prop> isOk readCondition "[[ echo_rc -eq 0 ]]" --- prop> isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]" --- prop> isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]" --- prop> isOk readCondition "[[ -v arr[$var] ]]" +-- >>> prop $ isOk readCondition "[ \\( a = b \\) -a \\( c = d \\) ]" +-- >>> prop $ isOk readCondition "[[ (a = b) || (c = d) ]]" +-- >>> prop $ isOk readCondition "[[ $c = [[:alpha:].~-] ]]" +-- >>> prop $ isOk readCondition "[[ $c =~ *foo* ]]" +-- >>> prop $ isOk readCondition "[[ $c =~ f( ]] )* ]]" +-- >>> prop $ isOk readCondition "[[ $c =~ a(b) ]]" +-- >>> prop $ isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]" +-- >>> prop $ isOk readCondition "[[ $c =~ ^[yY]$ ]]" +-- >>> prop $ isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]" +-- >>> prop $ isOk readCondition "[[ $l =~ ogg|flac ]]" +-- >>> prop $ isOk readCondition "[ foo -a -f bar ]" +-- >>> prop $ isOk readCondition "[[\na == b\n||\nc == d ]]" +-- >>> prop $ isOk readCondition "[[\na == b ||\nc == d ]]" +-- >>> prop $ isOk readCondition "[[ a == b\n||\nc == d ]]" +-- >>> prop $ isOk readCondition "[[ a == b ||\n c == d ]]" +-- >>> prop $ isWarning readCondition "[ a == b \n -o c == d ]" +-- >>> prop $ isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]" +-- >>> prop $ isOk readCondition "[ foo '>' bar ]" +-- >>> prop $ isOk readCondition "[ foo \">=\" bar ]" +-- >>> prop $ isOk readCondition "[ foo \\< bar ]" +-- >>> prop $ isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]" +-- >>> prop $ isOk readCondition "[ ]" +-- >>> prop $ isOk readCondition "[ '(' x \")\" ]" +-- >>> prop $ isOk readCondition "[[ echo_rc -eq 0 ]]" +-- >>> prop $ isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]" +-- >>> prop $ isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]" +-- >>> prop $ isOk readCondition "[[ -v arr[$var] ]]" readCondition = called "test expression" $ do opos <- getPosition start <- startSpan @@ -942,12 +946,12 @@ readAnnotationPrefix = do string "shellcheck" -- | --- prop> isOk readAnnotation "# shellcheck disable=1234,5678\n" --- prop> isOk readAnnotation "# shellcheck disable=SC1234 disable=SC5678\n" --- prop> isOk readAnnotation "# shellcheck disable=SC1234 source=/dev/null disable=SC5678\n" --- prop> isWarning readAnnotation "# shellcheck cats=dogs disable=SC1234\n" --- prop> isOk readAnnotation "# shellcheck disable=SC2002 # All cats are precious\n" --- prop> isOk readAnnotation "# shellcheck disable=SC1234 # shellcheck foo=bar\n" +-- >>> prop $ isOk readAnnotation "# shellcheck disable=1234,5678\n" +-- >>> prop $ isOk readAnnotation "# shellcheck disable=SC1234 disable=SC5678\n" +-- >>> prop $ isOk readAnnotation "# shellcheck disable=SC1234 source=/dev/null disable=SC5678\n" +-- >>> prop $ isWarning readAnnotation "# shellcheck cats=dogs disable=SC1234\n" +-- >>> prop $ isOk readAnnotation "# shellcheck disable=SC2002 # All cats are precious\n" +-- >>> prop $ isOk readAnnotation "# shellcheck disable=SC1234 # shellcheck foo=bar\n" readAnnotation = called "shellcheck directive" $ do try readAnnotationPrefix many1 linewhitespace @@ -1005,18 +1009,18 @@ readAnyComment = do many $ noneOf "\r\n" -- | --- prop> isOk readNormalWord "'foo'\"bar\"{1..3}baz$(lol)" --- prop> isOk readNormalWord "foo**(foo)!!!(@@(bar))" --- prop> isOk readNormalWord "foo#" --- prop> isOk readNormalWord "$\"foo\"$'foo\nbar'" --- prop> isWarning readNormalWord "${foo}}" --- prop> isOk readNormalWord "foo/{}" --- prop> isOk readNormalWord "foo\\\nbar" --- prop> isWarning readSubshell "(foo\\ \nbar)" --- prop> isOk readSubshell "(foo\\ ;\nbar)" --- prop> isWarning readNormalWord "\x201Chello\x201D" --- prop> isWarning readNormalWord "\x2018hello\x2019" --- prop> isWarning readNormalWord "hello\x2018" +-- >>> prop $ isOk readNormalWord "'foo'\"bar\"{1..3}baz$(lol)" +-- >>> prop $ isOk readNormalWord "foo**(foo)!!!(@@(bar))" +-- >>> prop $ isOk readNormalWord "foo#" +-- >>> prop $ isOk readNormalWord "$\"foo\"$'foo\nbar'" +-- >>> prop $ isWarning readNormalWord "${foo}}" +-- >>> prop $ isOk readNormalWord "foo/{}" +-- >>> prop $ isOk readNormalWord "foo\\\nbar" +-- >>> prop $ isWarning readSubshell "(foo\\ \nbar)" +-- >>> prop $ isOk readSubshell "(foo\\ ;\nbar)" +-- >>> prop $ isWarning readNormalWord "\x201Chello\x201D" +-- >>> prop $ isWarning readNormalWord "\x2018hello\x2019" +-- >>> prop $ isWarning readNormalWord "hello\x2018" readNormalWord = readNormalishWord "" readNormalishWord end = do @@ -1115,9 +1119,9 @@ readParamSubSpecialChar = do return $ T_ParamSubSpecialChar id x -- | --- prop> isOk readProcSub "<(echo test | wc -l)" --- prop> isOk readProcSub "<( if true; then true; fi )" --- prop> isOk readProcSub "<( # nothing here \n)" +-- >>> prop $ isOk readProcSub "<(echo test | wc -l)" +-- >>> prop $ isOk readProcSub "<( if true; then true; fi )" +-- >>> prop $ isOk readProcSub "<( # nothing here \n)" readProcSub = called "process substitution" $ do start <- startSpan dir <- try $ do @@ -1131,13 +1135,13 @@ readProcSub = called "process substitution" $ do return $ T_ProcSub id dir list -- | --- prop> isOk readSingleQuoted "'foo bar'" --- prop> isWarning readSingleQuoted "'foo bar\\'" --- prop> isWarning readNormalWord "'it's" --- prop> isWarning readSimpleCommand "foo='bar\ncow 'arg" --- prop> isOk readSimpleCommand "foo='bar cow 'arg" --- prop> isOk readSingleQuoted "'foo\x201C\&bar'" --- prop> isWarning readSingleQuoted "'foo\x2018\&bar'" +-- >>> prop $ isOk readSingleQuoted "'foo bar'" +-- >>> prop $ isWarning readSingleQuoted "'foo bar\\'" +-- >>> prop $ isWarning readNormalWord "'it's" +-- >>> prop $ isWarning readSimpleCommand "foo='bar\ncow 'arg" +-- >>> prop $ isOk readSimpleCommand "foo='bar cow 'arg" +-- >>> prop $ isOk readSingleQuoted "'foo\x201C\&bar'" +-- >>> prop $ isWarning readSingleQuoted "'foo\x2018\&bar'" readSingleQuoted = called "single quoted string" $ do start <- startSpan startPos <- getPosition @@ -1180,14 +1184,14 @@ readSingleQuotedPart = -- | --- prop> isOk (readBackTicked False) "`ls *.mp3`" --- prop> isOk (readBackTicked False) "`grep \"\\\"\"`" --- prop> isWarning (readBackTicked False) "´grep \"\\\"\"´" --- prop> isOk readSimpleCommand "`echo foo\necho bar`" --- prop> isOk readSimpleCommand "echo `foo`bar" --- prop> isWarning readSimpleCommand "echo `foo\necho `bar" --- prop> isOk readSimpleCommand "`#inline comment`" --- prop> isOk readSimpleCommand "echo `#comment` \\\nbar baz" +-- >>> prop $ isOk (readBackTicked False) "`ls *.mp3`" +-- >>> prop $ isOk (readBackTicked False) "`grep \"\\\"\"`" +-- >>> prop $ isWarning (readBackTicked False) "´grep \"\\\"\"´" +-- >>> prop $ isOk readSimpleCommand "`echo foo\necho bar`" +-- >>> prop $ isOk readSimpleCommand "echo `foo`bar" +-- >>> prop $ isWarning readSimpleCommand "echo `foo\necho `bar" +-- >>> prop $ isOk readSimpleCommand "`#inline comment`" +-- >>> prop $ isOk readSimpleCommand "echo `#comment` \\\nbar baz" readQuotedBackTicked = readBackTicked True readUnquotedBackTicked = readBackTicked False readBackTicked quoted = called "backtick expansion" $ do @@ -1254,15 +1258,15 @@ parseForgettingContext alsoOnSuccess parser = do fail "" -- | --- prop> isOk readDoubleQuoted "\"Hello $FOO\"" --- prop> isOk readDoubleQuoted "\"$'\"" --- prop> isOk readDoubleQuoted "\"\x2018hello\x2019\"" --- prop> isWarning readSimpleCommand "\"foo\nbar\"foo" --- prop> isOk readSimpleCommand "lol \"foo\nbar\" etc" --- prop> isOk readSimpleCommand "echo \"${ ls; }\"" --- prop> isOk readSimpleCommand "echo \"${ ls;}bar\"" --- prop> isWarning readDoubleQuoted "\"\x201Chello\x201D\"" --- prop> isOk readDoubleQuoted "\"foo\\\\n\"" +-- >>> prop $ isOk readDoubleQuoted "\"Hello $FOO\"" +-- >>> prop $ isOk readDoubleQuoted "\"$'\"" +-- >>> prop $ isOk readDoubleQuoted "\"\x2018hello\x2019\"" +-- >>> prop $ isWarning readSimpleCommand "\"foo\nbar\"foo" +-- >>> prop $ isOk readSimpleCommand "lol \"foo\nbar\" etc" +-- >>> prop $ isOk readSimpleCommand "echo \"${ ls; }\"" +-- >>> prop $ isOk readSimpleCommand "echo \"${ ls;}bar\"" +-- >>> prop $ isWarning readDoubleQuoted "\"\x201Chello\x201D\"" +-- >>> prop $ isOk readDoubleQuoted "\"foo\\\\n\"" readDoubleQuoted = called "double quoted string" $ do start <- startSpan startPos <- getPosition @@ -1316,14 +1320,14 @@ readNormalLiteral end = do return $ T_Literal id (concat s) -- | --- prop> isOk readGlob "*" --- prop> isOk readGlob "[^0-9]" --- prop> isOk readGlob "[a[:alpha:]]" --- prop> isOk readGlob "[[:alnum:]]" --- prop> isOk readGlob "[^[:alpha:]1-9]" --- prop> isOk readGlob "[\\|]" --- prop> isOk readGlob "[^[]" --- prop> isOk readGlob "[*?]" +-- >>> prop $ isOk readGlob "*" +-- >>> prop $ isOk readGlob "[^0-9]" +-- >>> prop $ isOk readGlob "[a[:alpha:]]" +-- >>> prop $ isOk readGlob "[[:alnum:]]" +-- >>> prop $ isOk readGlob "[^[:alpha:]1-9]" +-- >>> prop $ isOk readGlob "[\\|]" +-- >>> prop $ isOk readGlob "[^[]" +-- >>> prop $ isOk readGlob "[*?]" readGlob = readExtglob <|> readSimple <|> readClass <|> readGlobbyLiteral where readSimple = do @@ -1392,13 +1396,13 @@ readNormalEscaped = called "escaped char" $ do -- | --- prop> isOk readExtglob "!(*.mp3)" --- prop> isOk readExtglob "!(*.mp3|*.wmv)" --- prop> isOk readExtglob "+(foo \\) bar)" --- prop> isOk readExtglob "+(!(foo *(bar)))" --- prop> isOk readExtglob "*(((||))|())" --- prop> isOk readExtglob "*(<>)" --- prop> isOk readExtglob "@(|*())" +-- >>> prop $ isOk readExtglob "!(*.mp3)" +-- >>> prop $ isOk readExtglob "!(*.mp3|*.wmv)" +-- >>> prop $ isOk readExtglob "+(foo \\) bar)" +-- >>> prop $ isOk readExtglob "+(!(foo *(bar)))" +-- >>> prop $ isOk readExtglob "*(((||))|())" +-- >>> prop $ isOk readExtglob "*(<>)" +-- >>> prop $ isOk readExtglob "@(|*())" readExtglob = called "extglob" $ do start <- startSpan c <- try $ do @@ -1475,14 +1479,14 @@ readGenericEscaped = do return $ if x == '\n' then [] else ['\\', x] -- | --- prop> isOk readBraced "{1..4}" --- prop> isOk readBraced "{foo,bar,\"baz lol\"}" --- prop> isOk readBraced "{1,\\},2}" --- prop> isOk readBraced "{1,{2,3}}" --- prop> isOk readBraced "{JP{,E}G,jp{,e}g}" --- prop> isOk readBraced "{foo,bar,$((${var}))}" --- prop> isNotOk readBraced "{}" --- prop> isNotOk readBraced "{foo}" +-- >>> prop $ isOk readBraced "{1..4}" +-- >>> prop $ isOk readBraced "{foo,bar,\"baz lol\"}" +-- >>> prop $ isOk readBraced "{1,\\},2}" +-- >>> prop $ isOk readBraced "{1,{2,3}}" +-- >>> prop $ isOk readBraced "{JP{,E}G,jp{,e}g}" +-- >>> prop $ isOk readBraced "{foo,bar,$((${var}))}" +-- >>> prop $ isNotOk readBraced "{}" +-- >>> prop $ isNotOk readBraced "{foo}" readBraced = try braceExpansion where braceExpansion = @@ -1523,9 +1527,9 @@ readDoubleQuotedDollar = do -- | --- prop> isOk readDollarExpression "$(((1) && 3))" --- prop> isWarning readDollarExpression "$(((1)) && 3)" --- prop> isWarning readDollarExpression "$((\"$@\" &); foo;)" +-- >>> prop $ isOk readDollarExpression "$(((1) && 3))" +-- >>> prop $ isWarning readDollarExpression "$(((1)) && 3)" +-- >>> prop $ isWarning readDollarExpression "$((\"$@\" &); foo;)" readDollarExpression :: Monad m => SCParser m Token readDollarExpression = do ensureDollar @@ -1537,7 +1541,7 @@ readDollarExp = arithmetic <|> readDollarExpansion <|> readDollarBracket <|> rea parseNoteAt pos WarningC 1102 "Shells disambiguate $(( differently or not at all. For $(command substition), add space after $( . For $((arithmetics)), fix parsing errors.") -- | --- prop> isOk readDollarSingleQuote "$'foo\\\'lol'" +-- >>> prop $ isOk readDollarSingleQuote "$'foo\\\'lol'" readDollarSingleQuote = called "$'..' expression" $ do start <- startSpan try $ string "$'" @@ -1547,7 +1551,7 @@ readDollarSingleQuote = called "$'..' expression" $ do return $ T_DollarSingleQuoted id str -- | --- prop> isOk readDollarDoubleQuote "$\"hello\"" +-- >>> prop $ isOk readDollarDoubleQuote "$\"hello\"" readDollarDoubleQuote = do lookAhead . try $ string "$\"" start <- startSpan @@ -1559,8 +1563,8 @@ readDollarDoubleQuote = do return $ T_DollarDoubleQuoted id x -- | --- prop> isOk readDollarArithmetic "$(( 3 * 4 +5))" --- prop> isOk readDollarArithmetic "$(((3*4)+(1*2+(3-1))))" +-- >>> prop $ isOk readDollarArithmetic "$(( 3 * 4 +5))" +-- >>> prop $ isOk readDollarArithmetic "$(((3*4)+(1*2+(3-1))))" readDollarArithmetic = called "$((..)) expression" $ do start <- startSpan try (string "$((") @@ -1580,7 +1584,7 @@ readDollarBracket = called "$[..] expression" $ do return (T_DollarBracket id c) -- | --- prop> isOk readArithmeticExpression "((a?b:c))" +-- >>> prop $ isOk readArithmeticExpression "((a?b:c))" readArithmeticExpression = called "((..)) command" $ do start <- startSpan try (string "((") @@ -1604,8 +1608,8 @@ readAmbiguous prefix expected alternative warner = do return t -- | --- prop> isOk readDollarBraceCommandExpansion "${ ls; }" --- prop> isOk readDollarBraceCommandExpansion "${\nls\n}" +-- >>> prop $ isOk readDollarBraceCommandExpansion "${ ls; }" +-- >>> prop $ isOk readDollarBraceCommandExpansion "${\nls\n}" readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do start <- startSpan try $ do @@ -1618,10 +1622,10 @@ readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do return $ T_DollarBraceCommandExpansion id term -- | --- prop> isOk readDollarBraced "${foo//bar/baz}" --- prop> isOk readDollarBraced "${foo/'{cow}'}" --- prop> isOk readDollarBraced "${foo%%$(echo cow\\})}" --- prop> isOk readDollarBraced "${foo#\\}}" +-- >>> prop $ isOk readDollarBraced "${foo//bar/baz}" +-- >>> prop $ isOk readDollarBraced "${foo/'{cow}'}" +-- >>> prop $ isOk readDollarBraced "${foo%%$(echo cow\\})}" +-- >>> prop $ isOk readDollarBraced "${foo#\\}}" readDollarBraced = called "parameter expansion" $ do start <- startSpan try (string "${") @@ -1631,9 +1635,9 @@ readDollarBraced = called "parameter expansion" $ do return $ T_DollarBraced id word -- | --- prop> isOk readDollarExpansion "$(echo foo; ls\n)" --- prop> isOk readDollarExpansion "$( )" --- prop> isOk readDollarExpansion "$( command \n#comment \n)" +-- >>> prop $ isOk readDollarExpansion "$(echo foo; ls\n)" +-- >>> prop $ isOk readDollarExpansion "$( )" +-- >>> prop $ isOk readDollarExpansion "$( command \n#comment \n)" readDollarExpansion = called "command expansion" $ do start <- startSpan try (string "$(") @@ -1643,11 +1647,11 @@ readDollarExpansion = called "command expansion" $ do return $ T_DollarExpansion id cmds -- | --- prop> isOk readDollarVariable "$@" --- prop> isOk (readDollarVariable >> anyChar) "$?!" --- prop> isWarning (readDollarVariable >> anyChar) "$10" --- prop> isWarning (readDollarVariable >> string "[@]") "$arr[@]" --- prop> isWarning (readDollarVariable >> string "[f") "$arr[f" +-- >>> prop $ isOk readDollarVariable "$@" +-- >>> prop $ isOk (readDollarVariable >> anyChar) "$?!" +-- >>> prop $ isWarning (readDollarVariable >> anyChar) "$10" +-- >>> prop $ isWarning (readDollarVariable >> string "[@]") "$arr[@]" +-- >>> prop $ isWarning (readDollarVariable >> string "[f") "$arr[f" readDollarVariable :: Monad m => SCParser m Token readDollarVariable = do start <- startSpan @@ -1697,25 +1701,25 @@ readDollarLonely = do return $ T_Literal id "$" -- | --- prop> isOk readScript "cat << foo\nlol\ncow\nfoo" --- prop> isNotOk readScript "cat <<- EOF\n cow\n EOF" --- prop> isOk readScript "cat << foo\n$\"\nfoo" --- prop> isNotOk readScript "cat << foo\n`\nfoo" --- prop> isOk readScript "cat <<- !foo\nbar\n!foo" --- prop> isOk readScript "cat << foo\\ bar\ncow\nfoo bar" --- prop> isOk readScript "cat << foo\n\\$(f ())\nfoo" --- prop> isOk readScript "cat <>bar\netc\nfoo" --- prop> isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n" --- prop> isOk readScript "if true; then cat << foo << bar; fi\nfoo\nbar\n" --- prop> isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n" --- prop> isOk readScript "cat << foo|cat\nbar\nfoo" --- prop> isOk readScript "cat <<'#!'\nHello World\n#!\necho Done" --- prop> isWarning readScript "cat << foo\nbar\nfoo \n" --- prop> isWarning readScript "cat < isOk readScript "cat <<- ' foo'\nbar\n foo\n" --- prop> isWarning readScript "cat <<- ' foo'\nbar\n foo\n foo\n" --- prop> isWarning readScript "cat << foo\n foo\n()\nfoo\n" --- prop> isOk readScript "# shellcheck disable=SC1039\ncat << foo\n foo\n()\nfoo\n" +-- >>> prop $ isOk readScript "cat << foo\nlol\ncow\nfoo" +-- >>> prop $ isNotOk readScript "cat <<- EOF\n cow\n EOF" +-- >>> prop $ isOk readScript "cat << foo\n$\"\nfoo" +-- >>> prop $ isNotOk readScript "cat << foo\n`\nfoo" +-- >>> prop $ isOk readScript "cat <<- !foo\nbar\n!foo" +-- >>> prop $ isOk readScript "cat << foo\\ bar\ncow\nfoo bar" +-- >>> prop $ isOk readScript "cat << foo\n\\$(f ())\nfoo" +-- >>> prop $ isOk readScript "cat <>bar\netc\nfoo" +-- >>> prop $ isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n" +-- >>> prop $ isOk readScript "if true; then cat << foo << bar; fi\nfoo\nbar\n" +-- >>> prop $ isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n" +-- >>> prop $ isOk readScript "cat << foo|cat\nbar\nfoo" +-- >>> prop $ isOk readScript "cat <<'#!'\nHello World\n#!\necho Done" +-- >>> prop $ isWarning readScript "cat << foo\nbar\nfoo \n" +-- >>> prop $ isWarning readScript "cat <>> prop $ isOk readScript "cat <<- ' foo'\nbar\n foo\n" +-- >>> prop $ isWarning readScript "cat <<- ' foo'\nbar\n foo\n foo\n" +-- >>> prop $ isWarning readScript "cat << foo\n foo\n()\nfoo\n" +-- >>> prop $ isOk readScript "# shellcheck disable=SC1039\ncat << foo\n foo\n()\nfoo\n" readHereDoc = called "here document" $ do pos <- getPosition try $ string "<<" @@ -1884,7 +1888,7 @@ readIoDuplicate = try $ do -- | --- prop> isOk readIoFile ">> \"$(date +%YYmmDD)\"" +-- >>> prop $ isOk readIoFile ">> \"$(date +%YYmmDD)\"" readIoFile = called "redirection" $ do start <- startSpan op <- readIoFileOp @@ -1905,13 +1909,13 @@ readIoSource = try $ do return x -- | --- prop> isOk readIoRedirect "3>&2" --- prop> isOk readIoRedirect "2> lol" --- prop> isOk readIoRedirect "4>&-" --- prop> isOk readIoRedirect "&> lol" --- prop> isOk readIoRedirect "{foo}>&2" --- prop> isOk readIoRedirect "{foo}<&-" --- prop> isOk readIoRedirect "{foo}>&1-" +-- >>> prop $ isOk readIoRedirect "3>&2" +-- >>> prop $ isOk readIoRedirect "2> lol" +-- >>> prop $ isOk readIoRedirect "4>&-" +-- >>> prop $ isOk readIoRedirect "&> lol" +-- >>> prop $ isOk readIoRedirect "{foo}>&2" +-- >>> prop $ isOk readIoRedirect "{foo}<&-" +-- >>> prop $ isOk readIoRedirect "{foo}>&1-" readIoRedirect = do start <- startSpan n <- readIoSource @@ -1924,7 +1928,7 @@ readIoRedirect = do readRedirectList = many1 readIoRedirect -- | --- prop> isOk readHereString "<<< \"Hello $world\"" +-- >>> prop $ isOk readHereString "<<< \"Hello $world\"" readHereString = called "here string" $ do start <- startSpan try $ string "<<<" @@ -1937,11 +1941,11 @@ readNewlineList = many1 ((linefeed <|> carriageReturn) `thenSkip` spacing) readLineBreak = optional readNewlineList -- | --- prop> isWarning readScript "a &; b" --- prop> isOk readScript "a & b" --- prop> isWarning readScript "a & b" --- prop> isWarning readScript "a > file; b" --- prop> isWarning readScript "curl https://example.com/?foo=moo&bar=cow" +-- >>> prop $ isWarning readScript "a &; b" +-- >>> prop $ isOk readScript "a & b" +-- >>> prop $ isWarning readScript "a & b" +-- >>> prop $ isWarning readScript "a > file; b" +-- >>> prop $ isWarning readScript "curl https://example.com/?foo=moo&bar=cow" readSeparatorOp = do notFollowedBy2 (void g_AND_IF <|> void readCaseSeparator) notFollowedBy2 (string "&>") @@ -1986,20 +1990,20 @@ readSeparator = return ('\n', (start, end)) -- | --- prop> isOk readSimpleCommand "echo test > file" --- prop> isOk readSimpleCommand "cmd &> file" --- prop> isOk readSimpleCommand "export foo=(bar baz)" --- prop> isOk readSimpleCommand "typeset -a foo=(lol)" --- prop> isOk readSimpleCommand "time if true; then echo foo; fi" --- prop> isOk readSimpleCommand "time -p ( ls -l; )" --- prop> isOk readSimpleCommand "\\ls" --- prop> isWarning readSimpleCommand "// Lol" --- prop> isWarning readSimpleCommand "/* Lolbert */" --- prop> isWarning readSimpleCommand "/**** Lolbert */" --- prop> isOk readSimpleCommand "/\\* foo" --- prop> isWarning readSimpleCommand "elsif foo" --- prop> isWarning readSimpleCommand "ElseIf foo" --- prop> isWarning readSimpleCommand "elseif[$i==2]" +-- >>> prop $ isOk readSimpleCommand "echo test > file" +-- >>> prop $ isOk readSimpleCommand "cmd &> file" +-- >>> prop $ isOk readSimpleCommand "export foo=(bar baz)" +-- >>> prop $ isOk readSimpleCommand "typeset -a foo=(lol)" +-- >>> prop $ isOk readSimpleCommand "time if true; then echo foo; fi" +-- >>> prop $ isOk readSimpleCommand "time -p ( ls -l; )" +-- >>> prop $ isOk readSimpleCommand "\\ls" +-- >>> prop $ isWarning readSimpleCommand "// Lol" +-- >>> prop $ isWarning readSimpleCommand "/* Lolbert */" +-- >>> prop $ isWarning readSimpleCommand "/**** Lolbert */" +-- >>> prop $ isOk readSimpleCommand "/\\* foo" +-- >>> prop $ isWarning readSimpleCommand "elsif foo" +-- >>> prop $ isWarning readSimpleCommand "ElseIf foo" +-- >>> prop $ isWarning readSimpleCommand "elseif[$i==2]" readSimpleCommand = called "simple command" $ do prefix <- option [] readCmdPrefix skipAnnotationAndWarn @@ -2126,9 +2130,9 @@ readSource t = return t -- | --- prop> isOk readPipeline "! cat /etc/issue | grep -i ubuntu" --- prop> isWarning readPipeline "!cat /etc/issue | grep -i ubuntu" --- prop> isOk readPipeline "for f; do :; done|cat" +-- >>> prop $ isOk readPipeline "! cat /etc/issue | grep -i ubuntu" +-- >>> prop $ isWarning readPipeline "!cat /etc/issue | grep -i ubuntu" +-- >>> prop $ isOk readPipeline "for f; do :; done|cat" readPipeline = do unexpecting "keyword/token" readKeyword do @@ -2139,9 +2143,9 @@ readPipeline = do readPipeSequence -- | --- prop> isOk readAndOr "grep -i lol foo || exit 1" --- prop> isOk readAndOr "# shellcheck disable=1\nfoo" --- prop> isOk readAndOr "# shellcheck disable=1\n# lol\n# shellcheck disable=3\nfoo" +-- >>> prop $ isOk readAndOr "grep -i lol foo || exit 1" +-- >>> prop $ isOk readAndOr "# shellcheck disable=1\nfoo" +-- >>> prop $ isOk readAndOr "# shellcheck disable=1\n# lol\n# shellcheck disable=3\nfoo" readAndOr = do start <- startSpan apos <- getPosition @@ -2170,7 +2174,7 @@ readTermOrNone = do return [] -- | --- prop> isOk readTerm "time ( foo; bar; )" +-- >>> prop $ isOk readTerm "time ( foo; bar; )" readTerm = do allspacing m <- readAndOr @@ -2242,11 +2246,11 @@ skipAnnotationAndWarn = optional $ do readAnyComment -- | --- prop> isOk readIfClause "if false; then foo; elif true; then stuff; more stuff; else cows; fi" --- prop> isWarning readIfClause "if false; then; echo oo; fi" --- prop> isWarning readIfClause "if false; then true; else; echo lol; fi" --- prop> isWarning readIfClause "if false; then true; else if true; then echo lol; fi; fi" --- prop> isOk readIfClause "if false; then true; else\nif true; then echo lol; fi; fi" +-- >>> prop $ isOk readIfClause "if false; then foo; elif true; then stuff; more stuff; else cows; fi" +-- >>> prop $ isWarning readIfClause "if false; then; echo oo; fi" +-- >>> prop $ isWarning readIfClause "if false; then true; else; echo lol; fi" +-- >>> prop $ isWarning readIfClause "if false; then true; else if true; then echo lol; fi; fi" +-- >>> prop $ isOk readIfClause "if false; then true; else\nif true; then echo lol; fi; fi" readIfClause = called "if expression" $ do start <- startSpan pos <- getPosition @@ -2322,7 +2326,7 @@ ifNextToken parser action = action -- | --- prop> isOk readSubshell "( cd /foo; tar cf stuff.tar * )" +-- >>> prop $ isOk readSubshell "( cd /foo; tar cf stuff.tar * )" readSubshell = called "explicit subshell" $ do start <- startSpan char '(' @@ -2334,9 +2338,9 @@ readSubshell = called "explicit subshell" $ do return $ T_Subshell id list -- | --- prop> isOk readBraceGroup "{ a; b | c | d; e; }" --- prop> isWarning readBraceGroup "{foo;}" --- prop> isOk readBraceGroup "{(foo)}" +-- >>> prop $ isOk readBraceGroup "{ a; b | c | d; e; }" +-- >>> prop $ isWarning readBraceGroup "{foo;}" +-- >>> prop $ isOk readBraceGroup "{(foo)}" readBraceGroup = called "brace group" $ do start <- startSpan char '{' @@ -2355,7 +2359,7 @@ readBraceGroup = called "brace group" $ do return $ T_BraceGroup id list -- | --- prop> isOk readWhileClause "while [[ -e foo ]]; do sleep 1; done" +-- >>> prop $ isOk readWhileClause "while [[ -e foo ]]; do sleep 1; done" readWhileClause = called "while loop" $ do start <- startSpan kwId <- getId <$> g_While @@ -2365,7 +2369,7 @@ readWhileClause = called "while loop" $ do return $ T_WhileExpression id condition statements -- | --- prop> isOk readUntilClause "until kill -0 $PID; do sleep 1; done" +-- >>> prop $ isOk readUntilClause "until kill -0 $PID; do sleep 1; done" readUntilClause = called "until loop" $ do start <- startSpan kwId <- getId <$> g_Until @@ -2399,17 +2403,17 @@ readDoGroup kwId = do -- | --- prop> isOk readForClause "for f in *; do rm \"$f\"; done" --- prop> isOk readForClause "for f; do foo; done" --- prop> isOk readForClause "for((i=0; i<10; i++)); do echo $i; done" --- prop> isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \necho $i\ndone" --- prop> isOk readForClause "for ((;;))\ndo echo $i\ndone" --- prop> isOk readForClause "for ((;;)) do echo $i\ndone" --- prop> isOk readForClause "for ((;;)) ; do echo $i\ndone" --- prop> isOk readForClause "for i do true; done" --- prop> isOk readForClause "for ((;;)) { true; }" --- prop> isWarning readForClause "for $a in *; do echo \"$a\"; done" --- prop> isOk readForClause "for foo\nin\\\n bar\\\n baz\ndo true; done" +-- >>> prop $ isOk readForClause "for f in *; do rm \"$f\"; done" +-- >>> prop $ isOk readForClause "for f; do foo; done" +-- >>> prop $ isOk readForClause "for((i=0; i<10; i++)); do echo $i; done" +-- >>> prop $ isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \necho $i\ndone" +-- >>> prop $ isOk readForClause "for ((;;))\ndo echo $i\ndone" +-- >>> prop $ isOk readForClause "for ((;;)) do echo $i\ndone" +-- >>> prop $ isOk readForClause "for ((;;)) ; do echo $i\ndone" +-- >>> prop $ isOk readForClause "for i do true; done" +-- >>> prop $ isOk readForClause "for ((;;)) { true; }" +-- >>> prop $ isWarning readForClause "for $a in *; do echo \"$a\"; done" +-- >>> prop $ isOk readForClause "for foo\nin\\\n bar\\\n baz\ndo true; done" readForClause = called "for loop" $ do pos <- getPosition (T_For id) <- g_For @@ -2443,8 +2447,8 @@ readForClause = called "for loop" $ do return $ T_ForIn id name values group -- | --- prop> isOk readSelectClause "select foo in *; do echo $foo; done" --- prop> isOk readSelectClause "select foo; do echo $foo; done" +-- >>> prop $ isOk readSelectClause "select foo in *; do echo $foo; done" +-- >>> prop $ isOk readSelectClause "select foo; do echo $foo; done" readSelectClause = called "select loop" $ do (T_Select id) <- g_Select spacing @@ -2474,11 +2478,11 @@ readInClause = do return things -- | --- prop> isOk readCaseClause "case foo in a ) lol; cow;; b|d) fooo; esac" --- prop> isOk readCaseClause "case foo\n in * ) echo bar;; esac" --- prop> isOk readCaseClause "case foo\n in * ) echo bar & ;; esac" --- prop> isOk readCaseClause "case foo\n in *) echo bar ;& bar) foo; esac" --- prop> isOk readCaseClause "case foo\n in *) echo bar;;& foo) baz;; esac" +-- >>> prop $ isOk readCaseClause "case foo in a ) lol; cow;; b|d) fooo; esac" +-- >>> prop $ isOk readCaseClause "case foo\n in * ) echo bar;; esac" +-- >>> prop $ isOk readCaseClause "case foo\n in * ) echo bar & ;; esac" +-- >>> prop $ isOk readCaseClause "case foo\n in *) echo bar ;& bar) foo; esac" +-- >>> prop $ isOk readCaseClause "case foo\n in *) echo bar;;& foo) baz;; esac" readCaseClause = called "case expression" $ do start <- startSpan g_Case @@ -2523,18 +2527,18 @@ readCaseSeparator = choice [ ] -- | --- prop> isOk readFunctionDefinition "foo() { command foo --lol \"$@\"; }" --- prop> isOk readFunctionDefinition "foo (){ command foo --lol \"$@\"; }" --- prop> isWarning readFunctionDefinition "foo(a, b) { true; }" --- prop> isOk readFunctionDefinition ":(){ :|:;}" --- prop> isOk readFunctionDefinition "?(){ foo; }" --- prop> isOk readFunctionDefinition "..(){ cd ..; }" --- prop> isOk readFunctionDefinition "foo() (ls)" --- prop> isOk readFunctionDefinition "function foo { true; }" --- prop> isOk readFunctionDefinition "function foo () { true; }" --- prop> isWarning readFunctionDefinition "function foo{\ntrue\n}" --- prop> isOk readFunctionDefinition "function []!() { true; }" --- prop> isOk readFunctionDefinition "@require(){ true; }" +-- >>> prop $ isOk readFunctionDefinition "foo() { command foo --lol \"$@\"; }" +-- >>> prop $ isOk readFunctionDefinition "foo (){ command foo --lol \"$@\"; }" +-- >>> prop $ isWarning readFunctionDefinition "foo(a, b) { true; }" +-- >>> prop $ isOk readFunctionDefinition ":(){ :|:;}" +-- >>> prop $ isOk readFunctionDefinition "?(){ foo; }" +-- >>> prop $ isOk readFunctionDefinition "..(){ cd ..; }" +-- >>> prop $ isOk readFunctionDefinition "foo() (ls)" +-- >>> prop $ isOk readFunctionDefinition "function foo { true; }" +-- >>> prop $ isOk readFunctionDefinition "function foo () { true; }" +-- >>> prop $ isWarning readFunctionDefinition "function foo{\ntrue\n}" +-- >>> prop $ isOk readFunctionDefinition "function []!() { true; }" +-- >>> prop $ isOk readFunctionDefinition "@require(){ true; }" readFunctionDefinition = called "function" $ do start <- startSpan functionSignature <- try readFunctionSignature @@ -2577,9 +2581,9 @@ readFunctionDefinition = called "function" $ do return () -- | --- prop> isOk readCoProc "coproc foo { echo bar; }" --- prop> isOk readCoProc "coproc { echo bar; }" --- prop> isOk readCoProc "coproc echo bar" +-- >>> prop $ isOk readCoProc "coproc foo { echo bar; }" +-- >>> prop $ isOk readCoProc "coproc { echo bar; }" +-- >>> prop $ isOk readCoProc "coproc echo bar" readCoProc = called "coproc" $ do start <- startSpan try $ do @@ -2607,7 +2611,7 @@ readCoProc = called "coproc" $ do readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing) -- | --- prop> isOk readCompoundCommand "{ echo foo; }>/dev/null" +-- >>> prop $ isOk readCompoundCommand "{ echo foo; }>/dev/null" readCompoundCommand = do cmd <- choice [ readBraceGroup, @@ -2693,24 +2697,24 @@ readStringForParser parser = do readUntil endPos = anyChar `reluctantlyTill` (getPosition >>= guard . (== endPos)) -- | --- prop> isOk readAssignmentWord "a=42" --- prop> isOk readAssignmentWord "b=(1 2 3)" --- prop> isWarning readAssignmentWord "$b = 13" --- prop> isWarning readAssignmentWord "b = $(lol)" --- prop> isOk readAssignmentWord "b+=lol" --- prop> isWarning readAssignmentWord "b += (1 2 3)" --- prop> isOk readAssignmentWord "a[3$n'']=42" --- prop> isOk readAssignmentWord "a[4''$(cat foo)]=42" --- prop> isOk readAssignmentWord "IFS= " --- prop> isOk readAssignmentWord "foo=" --- prop> isOk readAssignmentWord "foo= " --- prop> isOk readAssignmentWord "foo= #bar" --- prop> isWarning readAssignmentWord "foo$n=42" --- prop> isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )" --- prop> isOk readAssignmentWord "a[b <<= 3 + c]='thing'" --- prop> isOk readAssignmentWord "var=( (1 2) (3 4) )" --- prop> isOk readAssignmentWord "var=( 1 [2]=(3 4) )" --- prop> isOk readAssignmentWord "var=(1 [2]=(3 4))" +-- >>> prop $ isOk readAssignmentWord "a=42" +-- >>> prop $ isOk readAssignmentWord "b=(1 2 3)" +-- >>> prop $ isWarning readAssignmentWord "$b = 13" +-- >>> prop $ isWarning readAssignmentWord "b = $(lol)" +-- >>> prop $ isOk readAssignmentWord "b+=lol" +-- >>> prop $ isWarning readAssignmentWord "b += (1 2 3)" +-- >>> prop $ isOk readAssignmentWord "a[3$n'']=42" +-- >>> prop $ isOk readAssignmentWord "a[4''$(cat foo)]=42" +-- >>> prop $ isOk readAssignmentWord "IFS= " +-- >>> prop $ isOk readAssignmentWord "foo=" +-- >>> prop $ isOk readAssignmentWord "foo= " +-- >>> prop $ isOk readAssignmentWord "foo= #bar" +-- >>> prop $ isWarning readAssignmentWord "foo$n=42" +-- >>> prop $ isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )" +-- >>> prop $ isOk readAssignmentWord "a[b <<= 3 + c]='thing'" +-- >>> prop $ isOk readAssignmentWord "var=( (1 2) (3 4) )" +-- >>> prop $ isOk readAssignmentWord "var=( 1 [2]=(3 4) )" +-- >>> prop $ isOk readAssignmentWord "var=(1 [2]=(3 4))" readAssignmentWord = readAssignmentWordExt True readWellFormedAssignment = readAssignmentWordExt False readAssignmentWordExt lenient = try $ do @@ -2904,13 +2908,13 @@ ifParse p t f = (lookAhead (try p) >> t) <|> f -- | --- prop> isOk readShebang "#!/bin/sh\n" --- prop> isWarning readShebang "!# /bin/sh\n" --- prop> isNotOk readShebang "#shellcheck shell=/bin/sh\n" --- prop> isWarning readShebang "! /bin/sh" --- prop> isWarning readShebang "\n#!/bin/sh" --- prop> isWarning readShebang " # Copyright \n!#/bin/bash" --- prop> isNotOk readShebang "# Copyright \nfoo\n#!/bin/bash" +-- >>> prop $ isOk readShebang "#!/bin/sh\n" +-- >>> prop $ isWarning readShebang "!# /bin/sh\n" +-- >>> prop $ isNotOk readShebang "#shellcheck shell=/bin/sh\n" +-- >>> prop $ isWarning readShebang "! /bin/sh" +-- >>> prop $ isWarning readShebang "\n#!/bin/sh" +-- >>> prop $ isWarning readShebang " # Copyright \n!#/bin/bash" +-- >>> prop $ isNotOk readShebang "# Copyright \nfoo\n#!/bin/bash" readShebang = do anyShebang <|> try readMissingBang <|> withHeader many linewhitespace @@ -2994,11 +2998,11 @@ verifyEof = eof <|> choice [ action -- | --- prop> isOk readScriptFile "#!/bin/bash\necho hello world\n" --- prop> isWarning readScriptFile "#!/bin/bash\r\necho hello world\n" --- prop> isWarning readScriptFile "#!/bin/bash\necho hello\xA0world" --- prop> isWarning readScriptFile "#!/usr/bin/perl\nfoo=(" --- prop> isOk readScriptFile "#!/bin/bash\n#This is an empty script\n\n" +-- >>> prop $ isOk readScriptFile "#!/bin/bash\necho hello world\n" +-- >>> prop $ isWarning readScriptFile "#!/bin/bash\r\necho hello world\n" +-- >>> prop $ isWarning readScriptFile "#!/bin/bash\necho hello\xA0world" +-- >>> prop $ isWarning readScriptFile "#!/usr/bin/perl\nfoo=(" +-- >>> prop $ isOk readScriptFile "#!/bin/bash\n#This is an empty script\n\n" readScriptFile = do start <- startSpan pos <- getPosition