diff --git a/.gitignore b/.gitignore index 6d5f1ae..ae6bd07 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ cabal-dev cabal.sandbox.config cabal.config .stack-work +dist-newstyle/ +.ghc.environment.* +cabal.project.local ### Snap ### /snap/.snapcraft/ diff --git a/Dockerfile b/Dockerfile index 2b65291..6db2840 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,20 +3,53 @@ FROM ubuntu:18.04 AS build USER root WORKDIR /opt/shellCheck -# Install OS deps -RUN apt-get update && apt-get install -y ghc cabal-install +# Install OS deps, including GHC from HVR-PPA +# https://launchpad.net/~hvr/+archive/ubuntu/ghc +RUN apt-get -yq update \ + && apt-get -yq install software-properties-common \ + && apt-add-repository -y "ppa:hvr/ghc" \ + && apt-get -yq update \ + && apt-get -yq install cabal-install-2.4 ghc-8.4.3 pandoc \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="/opt/ghc/bin:${PATH}" + +# Use gold linker and check tools versions +RUN ln -s $(which ld.gold) /usr/local/bin/ld && \ + cabal --version \ + && ghc --version \ + && ld --version # Install Haskell deps # (This is a separate copy/run so that source changes don't require rebuilding) +# +# We also patch regex-tdfa and aeson removing hard-coded -O2 flag. +# This makes compilation faster and binary smaller. +# Performance loss is unnoticeable for ShellCheck +# +# Remember to update versions, once in a while. COPY ShellCheck.cabal ./ -RUN cabal update && cabal install --dependencies-only --ghc-options="-optlo-Os -split-sections" +RUN cabal update && \ + cabal get regex-tdfa-1.2.3.1 && sed -i 's/-O2//' regex-tdfa-1.2.3.1/regex-tdfa.cabal && \ + cabal get aeson-1.4.0.0 && sed -i 's/-O2//' aeson-1.4.0.0/aeson.cabal && \ + echo 'packages: . regex-tdfa-1.2.3.1 aeson-1.4.0.0 > cabal.project' && \ + cabal new-build --dependencies-only \ + --disable-executable-dynamic --enable-split-sections --disable-tests # Copy source and build it -COPY LICENSE Setup.hs shellcheck.hs ./ +COPY LICENSE Setup.hs shellcheck.hs shellcheck.1.md ./ COPY src src -RUN cabal build Paths_ShellCheck && \ - ghc -optl-static -optl-pthread -isrc -idist/build/autogen --make shellcheck -split-sections -optc-Wl,--gc-sections -optlo-Os && \ - strip --strip-all shellcheck +COPY test test +# This SED is the only "nastyness" we have to do +# Hopefully soon we could add per-component ld-options to cabal.project +RUN sed -i 's/-- STATIC/ld-options: -static -pthread -Wl,--gc-sections/' ShellCheck.cabal && \ + cat ShellCheck.cabal && \ + cabal new-build \ + --disable-executable-dynamic --enable-split-sections --disable-tests && \ + cp $(find dist-newstyle -type f -name shellcheck) . && \ + strip --strip-all shellcheck && \ + file shellcheck && \ + ls -l shellcheck RUN mkdir -p /out/bin && \ cp shellcheck /out/bin/ diff --git a/Setup.hs b/Setup.hs index a909cf6..229e8a6 100644 --- a/Setup.hs +++ b/Setup.hs @@ -1,3 +1,8 @@ +{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -Wall #-} + +module Main (main) where + import Distribution.PackageDescription ( HookedBuildInfo, emptyHookedBuildInfo ) @@ -9,12 +14,42 @@ import Distribution.Simple ( import Distribution.Simple.Setup ( SDistFlags ) import System.Process ( system ) +import System.Directory ( doesFileExist, getModificationTime ) +#ifndef MIN_VERSION_cabal_doctest +#define MIN_VERSION_cabal_doctest(x,y,z) 0 +#endif +#if MIN_VERSION_cabal_doctest(1,0,0) + +import Distribution.Extra.Doctest ( addDoctestsUserHook ) +main :: IO () +main = defaultMainWithHooks $ addDoctestsUserHook "doctests" myHooks + where + myHooks = simpleUserHooks { preSDist = myPreSDist } + +#else + +#ifdef MIN_VERSION_Cabal +-- If the macro is defined, we have new cabal-install, +-- but for some reason we don't have cabal-doctest in package-db +-- +-- Probably we are running cabal sdist, when otherwise using new-build +-- workflow +#warning You are configuring this package without cabal-doctest installed. \ + The doctests test-suite will not work as a result. \ + To fix this, install cabal-doctest before configuring. +#endif + +main :: IO () main = defaultMainWithHooks myHooks where myHooks = simpleUserHooks { preSDist = myPreSDist } +#endif + + + -- | This hook will be executed before e.g. @cabal sdist@. It runs -- pandoc to create the man page from shellcheck.1.md. If the pandoc -- command is not found, this will fail with an error message: @@ -27,10 +62,20 @@ main = defaultMainWithHooks myHooks -- myPreSDist :: Args -> SDistFlags -> IO HookedBuildInfo myPreSDist _ _ = do - putStrLn "Building the man page (shellcheck.1) with pandoc..." - putStrLn pandoc_cmd - result <- system pandoc_cmd - putStrLn $ "pandoc exited with " ++ show result + exists <- doesFileExist "shellcheck.1" + if exists + then do + source <- getModificationTime "shellcheck.1.md" + target <- getModificationTime "shellcheck.1" + if target < source + then makeManPage + else putStrLn "shellcheck.1 is more recent than shellcheck.1.md" + else makeManPage return emptyHookedBuildInfo where - pandoc_cmd = "pandoc -s -f markdown-smart -t man shellcheck.1.md -o shellcheck.1" + makeManPage = do + putStrLn "Building the man page (shellcheck.1) with pandoc..." + putStrLn pandoc_cmd + result <- system pandoc_cmd + putStrLn $ "pandoc exited with " ++ show result + pandoc_cmd = "pandoc -s -t man shellcheck.1.md -o shellcheck.1" diff --git a/ShellCheck.cabal b/ShellCheck.cabal index 721da3f..00dea36 100644 --- a/ShellCheck.cabal +++ b/ShellCheck.cabal @@ -28,16 +28,14 @@ Extra-Source-Files: shellcheck.1.md -- built with a cabal sdist hook shellcheck.1 - -- convenience script for stripping tests - striptests - -- tests - test/shellcheck.hs custom-setup setup-depends: - base >= 4 && <5, - process >= 1.0 && <1.7, - Cabal >= 1.10 && <2.5 + base >= 4 && <5, + directory >= 1.2 && <1.4, + process >= 1.0 && <1.7, + cabal-doctest >= 1.0.6 && <1.1, + Cabal >= 1.10 && <2.5 source-repository head type: git @@ -60,7 +58,6 @@ library mtl >= 2.2.1, parsec, regex-tdfa, - QuickCheck >= 2.7.4, -- When cabal supports it, move this to setup-depends: process exposed-modules: @@ -98,23 +95,23 @@ executable shellcheck directory, mtl >= 2.2.1, parsec >= 3.0, - QuickCheck >= 2.7.4, regex-tdfa main-is: shellcheck.hs -test-suite test-shellcheck - type: exitcode-stdio-1.0 - build-depends: - aeson, - base >= 4 && < 5, - bytestring, - deepseq >= 1.4.0.0, - ShellCheck, - containers, - directory, - mtl >= 2.2.1, - parsec, - QuickCheck >= 2.7.4, - regex-tdfa - main-is: test/shellcheck.hs + -- Marker to add flags for static linking + -- STATIC +test-suite doctests + type: exitcode-stdio-1.0 + main-is: doctests.hs + build-depends: + base, + doctest >= 0.16.0 && <0.17, + QuickCheck >=2.11 && <2.13, + ShellCheck, + template-haskell + + x-doctest-options: --fast + + ghc-options: -Wall -threaded + hs-source-dirs: test diff --git a/quickrun b/quickrun index 172ae88..f53f1b5 100755 --- a/quickrun +++ b/quickrun @@ -3,3 +3,10 @@ # This allows testing changes without recompiling. runghc -isrc -idist/build/autogen shellcheck.hs "$@" + +# Note: with new-build you can +# +# % cabal new-run --disable-optimization -- shellcheck "$@" +# +# This does build the executable, but as the optimisation is disabled, +# the build is quite fast. diff --git a/quicktest b/quicktest index 4f0702d..7d7cb05 100755 --- a/quicktest +++ b/quicktest @@ -1,22 +1,21 @@ -#!/usr/bin/env bash -# quicktest runs the ShellCheck unit tests in an interpreted mode. -# This allows running tests without compiling, which can be faster. +#!/bin/bash +# shellcheck disable=SC2091 + +# quicktest runs the ShellCheck unit tests. +# Once `doctests` test executable is build, we can just run it +# This allows running tests without compiling library, which is faster. # 'cabal test' remains the source of truth. -( - var=$(echo 'liftM and $ sequence [ - ShellCheck.Analytics.runTests - ,ShellCheck.Parser.runTests - ,ShellCheck.Checker.runTests - ,ShellCheck.Checks.Commands.runTests - ,ShellCheck.Checks.ShellSupport.runTests - ,ShellCheck.AnalyzerLib.runTests - ]' | tr -d '\n' | cabal repl ShellCheck 2>&1 | tee /dev/stderr) -if [[ $var == *$'\nTrue'* ]] -then - exit 0 -else - grep -C 3 -e "Fail" -e "Tracing" <<< "$var" - exit 1 -fi -) 2>&1 +$(find dist -type f -name doctests) + +# Note: if you have build the project with new-build +# +# % cabal new-build -w ghc-8.4.3 --enable-tests +# +# and have cabal-plan installed (e.g. with cabal new-install cabal-plan), +# then you can quicktest with +# +# % $(cabal-plan list-bin doctests) +# +# Once the test executable exists, we can simply run it to perform doctests +# which use GHCi under the hood. diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index a164de1..a2d6152 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -17,9 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} -module ShellCheck.Analytics (runAnalytics, ShellCheck.Analytics.runTests) where +module ShellCheck.Analytics (runAnalytics) where import ShellCheck.AST import ShellCheck.ASTLib @@ -43,8 +42,6 @@ import Data.Maybe import Data.Ord import Debug.Trace import qualified Data.Map.Strict as Map -import Test.QuickCheck.All (forAllProperties) -import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess) -- Checks that are run on the AST root treeChecks :: [Parameters -> Token -> [TokenComment]] @@ -275,7 +272,7 @@ replaceEnd id params n r = surroundWidth id params s = fixWith [replaceStart id params 0 s, replaceEnd id params 0 s] fixWith fixes = newFix { fixReplacements = fixes } -prop_checkEchoWc3 = 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 @@ -288,20 +285,22 @@ checkEchoWc _ (T_Pipeline id _ [a, b]) = countMsg = style id 2000 "See if you can use ${#variable} instead." checkEchoWc _ _ = return () -prop_checkPipedAssignment1 = verify checkPipedAssignment "A=ls | grep foo" -prop_checkPipedAssignment2 = verifyNot checkPipedAssignment "A=foo cmd | grep foo" -prop_checkPipedAssignment3 = 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_checkAssignAteCommand1 = verify checkAssignAteCommand "A=ls -l" -prop_checkAssignAteCommand2 = verify checkAssignAteCommand "A=ls --sort=$foo" -prop_checkAssignAteCommand3 = verify checkAssignAteCommand "A=cat foo | grep bar" -prop_checkAssignAteCommand4 = verifyNot checkAssignAteCommand "A=foo ls -l" -prop_checkAssignAteCommand5 = verify checkAssignAteCommand "PAGER=cat grep bar" -prop_checkAssignAteCommand6 = verifyNot checkAssignAteCommand "PAGER=\"cat\" grep bar" -prop_checkAssignAteCommand7 = 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 @@ -320,9 +319,10 @@ checkAssignAteCommand _ (T_SimpleCommand id (T_Assignment _ _ _ _ assignmentTerm checkAssignAteCommand _ _ = return () -prop_checkArithmeticOpCommand1 = verify checkArithmeticOpCommand "i=i + 1" -prop_checkArithmeticOpCommand2 = verify checkArithmeticOpCommand "foo=bar * 2" -prop_checkArithmeticOpCommand3 = 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 @@ -332,8 +332,9 @@ checkArithmeticOpCommand _ (T_SimpleCommand id [T_Assignment {}] (firstWord:_)) "Use $((..)) for arithmetics, e.g. i=$((i " ++ op ++ " 2))" checkArithmeticOpCommand _ _ = return () -prop_checkWrongArit = verify checkWrongArithmeticAssignment "i=i+1" -prop_checkWrongArit2 = 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 @@ -361,12 +362,13 @@ checkWrongArithmeticAssignment params (T_SimpleCommand id (T_Assignment _ _ _ _ checkWrongArithmeticAssignment _ _ = return () -prop_checkUuoc1 = verify checkUuoc "cat foo | grep bar" -prop_checkUuoc2 = verifyNot checkUuoc "cat * | grep bar" -prop_checkUuoc3 = verify checkUuoc "cat $var | grep bar" -prop_checkUuoc4 = verifyNot checkUuoc "cat $var" -prop_checkUuoc5 = verifyNot checkUuoc "cat \"$@\"" -prop_checkUuoc6 = 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 @@ -376,20 +378,21 @@ checkUuoc _ (T_Pipeline _ _ (T_Redirecting _ _ cmd:_:_)) = isOption word = "-" `isPrefixOf` onlyLiteralString word checkUuoc _ _ = return () -prop_checkPipePitfalls3 = verify checkPipePitfalls "ls | grep -v mp3" -prop_checkPipePitfalls4 = verifyNot checkPipePitfalls "find . -print0 | xargs -0 foo" -prop_checkPipePitfalls5 = verifyNot checkPipePitfalls "ls -N | foo" -prop_checkPipePitfalls6 = verify checkPipePitfalls "find . | xargs foo" -prop_checkPipePitfalls7 = verifyNot checkPipePitfalls "find . -printf '%s\\n' | xargs foo" -prop_checkPipePitfalls8 = verify checkPipePitfalls "foo | grep bar | wc -l" -prop_checkPipePitfalls9 = verifyNot checkPipePitfalls "foo | grep -o bar | wc -l" -prop_checkPipePitfalls10 = verifyNot checkPipePitfalls "foo | grep -o bar | wc" -prop_checkPipePitfalls11 = verifyNot checkPipePitfalls "foo | grep bar | wc" -prop_checkPipePitfalls12 = verifyNot checkPipePitfalls "foo | grep -o bar | wc -c" -prop_checkPipePitfalls13 = verifyNot checkPipePitfalls "foo | grep bar | wc -c" -prop_checkPipePitfalls14 = verifyNot checkPipePitfalls "foo | grep -o bar | wc -cmwL" -prop_checkPipePitfalls15 = verifyNot checkPipePitfalls "foo | grep bar | wc -cmwL" -prop_checkPipePitfalls16 = 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:_) -> @@ -453,22 +456,24 @@ indexOfSublists sub = f 0 match _ _ = False -prop_checkShebangParameters1 = verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow" -prop_checkShebangParameters2 = 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_checkShebang1 = verifyNotTree checkShebang "#!/usr/bin/env bash -x\necho cow" -prop_checkShebang2 = verifyNotTree checkShebang "#! /bin/sh -l " -prop_checkShebang3 = verifyTree checkShebang "ls -l" -prop_checkShebang4 = verifyNotTree checkShebang "#shellcheck shell=sh\nfoo" -prop_checkShebang5 = verifyTree checkShebang "#!/usr/bin/env ash" -prop_checkShebang6 = verifyNotTree checkShebang "#!/usr/bin/env ash\n# shellcheck shell=dash\n" -prop_checkShebang7 = verifyNotTree checkShebang "#!/usr/bin/env ash\n# shellcheck shell=sh\n" -prop_checkShebang8 = verifyTree checkShebang "#!bin/sh\ntrue" -prop_checkShebang9 = verifyNotTree checkShebang "# shellcheck shell=sh\ntrue" -prop_checkShebang10= verifyNotTree checkShebang "#!foo\n# shellcheck shell=sh ignore=SC2239\ntrue" +-- | +-- >>> 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 $ verifyTree checkShebang "#!bin/sh\ntrue" +-- >>> prop $ verifyNotTree checkShebang "# shellcheck shell=sh\ntrue" +-- >>> prop $ verifyNotTree checkShebang "#!foo\n# shellcheck shell=sh ignore=SC2239\ntrue" checkShebang params (T_Annotation _ list t) = if any isOverride list then [] else checkShebang params t where @@ -484,15 +489,16 @@ checkShebang params (T_Script id sb _) = execWriter $ do err id 2239 "Ensure the shebang uses an absolute path to the interpreter." -prop_checkForInQuoted = verify checkForInQuoted "for f in \"$(ls)\"; do echo foo; done" -prop_checkForInQuoted2 = verifyNot checkForInQuoted "for f in \"$@\"; do echo foo; done" -prop_checkForInQuoted2a = verifyNot checkForInQuoted "for f in *.mp3; do echo foo; done" -prop_checkForInQuoted2b = verify checkForInQuoted "for f in \"*.mp3\"; do echo foo; done" -prop_checkForInQuoted3 = verify checkForInQuoted "for f in 'find /'; do true; done" -prop_checkForInQuoted4 = verify checkForInQuoted "for f in 1,2,3; do true; done" -prop_checkForInQuoted4a = verifyNot checkForInQuoted "for f in foo{1,2,3}; do true; done" -prop_checkForInQuoted5 = verify checkForInQuoted "for f in ls; do true; done" -prop_checkForInQuoted6 = 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)) $ @@ -506,11 +512,12 @@ checkForInQuoted _ (T_ForIn _ f [T_NormalWord _ [T_Literal id s]] _) = else warn id 2043 "This loop will only ever run once for a constant value. Did you perhaps mean to loop over dir/*, $var or $(cmd)?" checkForInQuoted _ _ = return () -prop_checkForInCat1 = verify checkForInCat "for f in $(cat foo); do stuff; done" -prop_checkForInCat1a= verify checkForInCat "for f in `cat foo`; do stuff; done" -prop_checkForInCat2 = verify checkForInCat "for f in $(cat foo | grep lol); do stuff; done" -prop_checkForInCat2a= verify checkForInCat "for f in `cat foo | grep lol`; do stuff; done" -prop_checkForInCat3 = 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]) @@ -522,9 +529,10 @@ checkForInCat _ (T_ForIn _ f [T_NormalWord _ w] _) = mapM_ checkF w ["grep", "fgrep", "egrep", "sed", "cat", "awk", "cut", "sort"] checkForInCat _ _ = return () -prop_checkForInLs = verify checkForInLs "for f in $(ls *.mp3); do mplayer \"$f\"; done" -prop_checkForInLs2 = verify checkForInLs "for f in `ls *.mp3`; do mplayer \"$f\"; done" -prop_checkForInLs3 = 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]]] _) = @@ -541,12 +549,13 @@ checkForInLs _ = try _ -> return () -prop_checkFindExec1 = verify checkFindExec "find / -name '*.php' -exec rm {};" -prop_checkFindExec2 = verify checkFindExec "find / -exec touch {} && ls {} \\;" -prop_checkFindExec3 = verify checkFindExec "find / -execdir cat {} | grep lol +" -prop_checkFindExec4 = verifyNot checkFindExec "find / -name '*.php' -exec foo {} +" -prop_checkFindExec5 = verifyNot checkFindExec "find / -execdir bash -c 'a && b' \\;" -prop_checkFindExec6 = 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 $ @@ -581,17 +590,18 @@ checkFindExec _ cmd@(T_SimpleCommand _ _ t@(h:r)) | cmd `isCommand` "find" = do checkFindExec _ _ = return () -prop_checkUnquotedExpansions1 = verify checkUnquotedExpansions "rm $(ls)" -prop_checkUnquotedExpansions1a= verify checkUnquotedExpansions "rm `ls`" -prop_checkUnquotedExpansions2 = verify checkUnquotedExpansions "rm foo$(date)" -prop_checkUnquotedExpansions3 = verify checkUnquotedExpansions "[ $(foo) == cow ]" -prop_checkUnquotedExpansions3a= verify checkUnquotedExpansions "[ ! $(foo) ]" -prop_checkUnquotedExpansions4 = verifyNot checkUnquotedExpansions "[[ $(foo) == cow ]]" -prop_checkUnquotedExpansions5 = verifyNot checkUnquotedExpansions "for f in $(cmd); do echo $f; done" -prop_checkUnquotedExpansions6 = verifyNot checkUnquotedExpansions "$(cmd)" -prop_checkUnquotedExpansions7 = verifyNot checkUnquotedExpansions "cat << foo\n$(ls)\nfoo" -prop_checkUnquotedExpansions8 = verifyNot checkUnquotedExpansions "set -- $(seq 1 4)" -prop_checkUnquotedExpansions9 = 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 @@ -608,14 +618,15 @@ checkUnquotedExpansions params = getCommandNameFromExpansion t == Just "seq" -prop_checkRedirectToSame = verify checkRedirectToSame "cat foo > foo" -prop_checkRedirectToSame2 = verify checkRedirectToSame "cat lol | sed -e 's/a/b/g' > lol" -prop_checkRedirectToSame3 = verifyNot checkRedirectToSame "cat lol | sed -e 's/a/b/g' > foo.bar && mv foo.bar lol" -prop_checkRedirectToSame4 = verifyNot checkRedirectToSame "foo /dev/null > /dev/null" -prop_checkRedirectToSame5 = verifyNot checkRedirectToSame "foo > bar 2> bar" -prop_checkRedirectToSame6 = verifyNot checkRedirectToSame "echo foo > foo" -prop_checkRedirectToSame7 = verifyNot checkRedirectToSame "sed 's/foo/bar/g' file | sponge file" -prop_checkRedirectToSame8 = 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 @@ -661,14 +672,15 @@ checkRedirectToSame params s@(T_Pipeline _ _ list) = checkRedirectToSame _ _ = return () -prop_checkShorthandIf = verify checkShorthandIf "[[ ! -z file ]] && scp file host || rm file" -prop_checkShorthandIf2 = verifyNot checkShorthandIf "[[ ! -z file ]] && { scp file host || echo 'Eek'; }" -prop_checkShorthandIf3 = verifyNot checkShorthandIf "foo && bar || echo baz" -prop_checkShorthandIf4 = verifyNot checkShorthandIf "foo && a=b || a=c" -prop_checkShorthandIf5 = verifyNot checkShorthandIf "foo && rm || printf b" -prop_checkShorthandIf6 = verifyNot checkShorthandIf "if foo && bar || baz; then true; fi" -prop_checkShorthandIf7 = verifyNot checkShorthandIf "while foo && bar || baz; do true; done" -prop_checkShorthandIf8 = 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." @@ -681,9 +693,10 @@ checkShorthandIf params x@(T_AndIf id _ (T_OrIf _ _ (T_Pipeline _ _ t))) checkShorthandIf _ _ = return () -prop_checkDollarStar = verify checkDollarStar "for f in $*; do ..; done" -prop_checkDollarStar2 = verifyNot checkDollarStar "a=$*" -prop_checkDollarStar3 = 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) $ @@ -691,17 +704,18 @@ checkDollarStar p t@(T_NormalWord _ [b@(T_DollarBraced id _)]) checkDollarStar _ _ = return () -prop_checkUnquotedDollarAt = verify checkUnquotedDollarAt "ls $@" -prop_checkUnquotedDollarAt1= verifyNot checkUnquotedDollarAt "ls ${#@}" -prop_checkUnquotedDollarAt2 = verify checkUnquotedDollarAt "ls ${foo[@]}" -prop_checkUnquotedDollarAt3 = verifyNot checkUnquotedDollarAt "ls ${#foo[@]}" -prop_checkUnquotedDollarAt4 = verifyNot checkUnquotedDollarAt "ls \"$@\"" -prop_checkUnquotedDollarAt5 = verifyNot checkUnquotedDollarAt "ls ${foo/@/ at }" -prop_checkUnquotedDollarAt6 = verifyNot checkUnquotedDollarAt "a=$@" -prop_checkUnquotedDollarAt7 = verify checkUnquotedDollarAt "for f in ${var[@]}; do true; done" -prop_checkUnquotedDollarAt8 = verifyNot checkUnquotedDollarAt "echo \"${args[@]:+${args[@]}}\"" -prop_checkUnquotedDollarAt9 = verifyNot checkUnquotedDollarAt "echo ${args[@]:+\"${args[@]}\"}" -prop_checkUnquotedDollarAt10 = 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) $ @@ -709,11 +723,12 @@ checkUnquotedDollarAt p word@(T_NormalWord _ parts) | not $ isStrictlyQuoteFree "Double quote array expansions to avoid re-splitting elements." checkUnquotedDollarAt _ _ = return () -prop_checkConcatenatedDollarAt1 = verify checkConcatenatedDollarAt "echo \"foo$@\"" -prop_checkConcatenatedDollarAt2 = verify checkConcatenatedDollarAt "echo ${arr[@]}lol" -prop_checkConcatenatedDollarAt3 = verify checkConcatenatedDollarAt "echo $a$@" -prop_checkConcatenatedDollarAt4 = verifyNot checkConcatenatedDollarAt "echo $@" -prop_checkConcatenatedDollarAt5 = 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) $ @@ -724,13 +739,14 @@ checkConcatenatedDollarAt p word@T_NormalWord {} for t = err (getId t) 2145 "Argument mixes string and array. Use * or separate argument." checkConcatenatedDollarAt _ _ = return () -prop_checkArrayAsString1 = verify checkArrayAsString "a=$@" -prop_checkArrayAsString2 = verify checkArrayAsString "a=\"${arr[@]}\"" -prop_checkArrayAsString3 = verify checkArrayAsString "a=*.png" -prop_checkArrayAsString4 = verify checkArrayAsString "a={1..10}" -prop_checkArrayAsString5 = verifyNot checkArrayAsString "a='*.gif'" -prop_checkArrayAsString6 = verifyNot checkArrayAsString "a=$*" -prop_checkArrayAsString7 = 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 @@ -742,15 +758,16 @@ checkArrayAsString _ (T_Assignment id _ _ _ word) = "Brace expansions and globs are literal in assignments. Quote it or use an array." checkArrayAsString _ _ = return () -prop_checkArrayWithoutIndex1 = verifyTree checkArrayWithoutIndex "foo=(a b); echo $foo" -prop_checkArrayWithoutIndex2 = verifyNotTree checkArrayWithoutIndex "foo='bar baz'; foo=($foo); echo ${foo[0]}" -prop_checkArrayWithoutIndex3 = verifyTree checkArrayWithoutIndex "coproc foo while true; do echo cow; done; echo $foo" -prop_checkArrayWithoutIndex4 = verifyTree checkArrayWithoutIndex "coproc tail -f log; echo $COPROC" -prop_checkArrayWithoutIndex5 = verifyTree checkArrayWithoutIndex "a[0]=foo; echo $a" -prop_checkArrayWithoutIndex6 = verifyTree checkArrayWithoutIndex "echo $PIPESTATUS" -prop_checkArrayWithoutIndex7 = verifyTree checkArrayWithoutIndex "a=(a b); a+=c" -prop_checkArrayWithoutIndex8 = verifyTree checkArrayWithoutIndex "declare -a foo; foo=bar;" -prop_checkArrayWithoutIndex9 = verifyTree checkArrayWithoutIndex "read -r -a arr <<< 'foo bar'; echo \"$arr\"" +-- | +-- >>> 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 "read -r -a arr <<< 'foo bar'; echo \"$arr\"" checkArrayWithoutIndex params _ = doVariableFlowAnalysis readF writeF defaultMap (variableFlow params) where @@ -785,13 +802,14 @@ checkArrayWithoutIndex params _ = T_Assignment _ _ _ (_:_) _ -> True _ -> False -prop_checkStderrRedirect = verify checkStderrRedirect "test 2>&1 > cow" -prop_checkStderrRedirect2 = verifyNot checkStderrRedirect "test > cow 2>&1" -prop_checkStderrRedirect3 = verifyNot checkStderrRedirect "test 2>&1 > file | grep stderr" -prop_checkStderrRedirect4 = verifyNot checkStderrRedirect "errors=$(test 2>&1 > file)" -prop_checkStderrRedirect5 = verifyNot checkStderrRedirect "read < <(test 2>&1 > file)" -prop_checkStderrRedirect6 = verify checkStderrRedirect "foo | bar 2>&1 > /dev/null" -prop_checkStderrRedirect7 = 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 _) @@ -818,27 +836,28 @@ lt x = trace ("Tracing " ++ show x) x ltt t = trace ("Tracing " ++ show t) -prop_checkSingleQuotedVariables = verify checkSingleQuotedVariables "echo '$foo'" -prop_checkSingleQuotedVariables2 = verify checkSingleQuotedVariables "echo 'lol$1.jpg'" -prop_checkSingleQuotedVariables3 = verifyNot checkSingleQuotedVariables "sed 's/foo$/bar/'" -prop_checkSingleQuotedVariables3a= verify checkSingleQuotedVariables "sed 's/${foo}/bar/'" -prop_checkSingleQuotedVariables3b= verify checkSingleQuotedVariables "sed 's/$(echo cow)/bar/'" -prop_checkSingleQuotedVariables3c= verify checkSingleQuotedVariables "sed 's/$((1+foo))/bar/'" -prop_checkSingleQuotedVariables4 = verifyNot checkSingleQuotedVariables "awk '{print $1}'" -prop_checkSingleQuotedVariables5 = verifyNot checkSingleQuotedVariables "trap 'echo $SECONDS' EXIT" -prop_checkSingleQuotedVariables6 = verifyNot checkSingleQuotedVariables "sed -n '$p'" -prop_checkSingleQuotedVariables6a= verify checkSingleQuotedVariables "sed -n '$pattern'" -prop_checkSingleQuotedVariables7 = verifyNot checkSingleQuotedVariables "PS1='$PWD \\$ '" -prop_checkSingleQuotedVariables8 = verify checkSingleQuotedVariables "find . -exec echo '$1' {} +" -prop_checkSingleQuotedVariables9 = verifyNot checkSingleQuotedVariables "find . -exec awk '{print $1}' {} \\;" -prop_checkSingleQuotedVariables10= verify checkSingleQuotedVariables "echo '`pwd`'" -prop_checkSingleQuotedVariables11= verifyNot checkSingleQuotedVariables "sed '${/lol/d}'" -prop_checkSingleQuotedVariables12= verifyNot checkSingleQuotedVariables "eval 'echo $1'" -prop_checkSingleQuotedVariables13= verifyNot checkSingleQuotedVariables "busybox awk '{print $1}'" -prop_checkSingleQuotedVariables14= verifyNot checkSingleQuotedVariables "[ -v 'bar[$foo]' ]" -prop_checkSingleQuotedVariables15= verifyNot checkSingleQuotedVariables "git filter-branch 'test $GIT_COMMIT'" -prop_checkSingleQuotedVariables16= verify checkSingleQuotedVariables "git '$a'" -prop_checkSingleQuotedVariables17= 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) $ @@ -905,29 +924,31 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) = checkSingleQuotedVariables _ _ = return () -prop_checkUnquotedN = verify checkUnquotedN "if [ -n $foo ]; then echo cow; fi" -prop_checkUnquotedN2 = verify checkUnquotedN "[ -n $cow ]" -prop_checkUnquotedN3 = verifyNot checkUnquotedN "[[ -n $foo ]] && echo cow" -prop_checkUnquotedN4 = verify checkUnquotedN "[ -n $cow -o -t 1 ]" -prop_checkUnquotedN5 = 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_checkNumberComparisons1 = verify checkNumberComparisons "[[ $foo < 3 ]]" -prop_checkNumberComparisons2 = verify checkNumberComparisons "[[ 0 >= $(cmd) ]]" -prop_checkNumberComparisons3 = verifyNot checkNumberComparisons "[[ $foo ]] > 3" -prop_checkNumberComparisons4 = verify checkNumberComparisons "[[ $foo > 2.72 ]]" -prop_checkNumberComparisons5 = verify checkNumberComparisons "[[ $foo -le 2.72 ]]" -prop_checkNumberComparisons6 = verify checkNumberComparisons "[[ 3.14 -eq $foo ]]" -prop_checkNumberComparisons7 = verifyNot checkNumberComparisons "[[ 3.14 == $foo ]]" -prop_checkNumberComparisons8 = verify checkNumberComparisons "[ foo <= bar ]" -prop_checkNumberComparisons9 = verify checkNumberComparisons "[ foo \\>= bar ]" -prop_checkNumberComparisons11 = verify checkNumberComparisons "[ $foo -eq 'N' ]" -prop_checkNumberComparisons12 = verify checkNumberComparisons "[ x$foo -gt x${N} ]" -prop_checkNumberComparisons13 = verify checkNumberComparisons "[ $foo > $bar ]" -prop_checkNumberComparisons14 = verifyNot checkNumberComparisons "[[ foo < bar ]]" -prop_checkNumberComparisons15 = 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 @@ -1010,24 +1031,27 @@ checkNumberComparisons params (TC_Binary id typ op lhs rhs) = do floatRegex = mkRegex "^[-+]?[0-9]+\\.[0-9]+$" checkNumberComparisons _ _ = return () -prop_checkSingleBracketOperators1 = 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_checkDoubleBracketOperators1 = verify checkDoubleBracketOperators "[[ 3 \\< 4 ]]" -prop_checkDoubleBracketOperators3 = 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_checkConditionalAndOrs1 = verify checkConditionalAndOrs "[ foo && bar ]" -prop_checkConditionalAndOrs2 = verify checkConditionalAndOrs "[[ foo -o bar ]]" -prop_checkConditionalAndOrs3 = verifyNot checkConditionalAndOrs "[[ foo || bar ]]" -prop_checkConditionalAndOrs4 = verify checkConditionalAndOrs "[ foo -a bar ]" -prop_checkConditionalAndOrs5 = 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 "&&" _ _) -> @@ -1046,12 +1070,13 @@ checkConditionalAndOrs _ t = _ -> return () -prop_checkQuotedCondRegex1 = verify checkQuotedCondRegex "[[ $foo =~ \"bar.*\" ]]" -prop_checkQuotedCondRegex2 = verify checkQuotedCondRegex "[[ $foo =~ '(cow|bar)' ]]" -prop_checkQuotedCondRegex3 = verifyNot checkQuotedCondRegex "[[ $foo =~ $foo ]]" -prop_checkQuotedCondRegex4 = verifyNot checkQuotedCondRegex "[[ $foo =~ \"bar\" ]]" -prop_checkQuotedCondRegex5 = verifyNot checkQuotedCondRegex "[[ $foo =~ 'cow bar' ]]" -prop_checkQuotedCondRegex6 = 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 @@ -1069,14 +1094,15 @@ checkQuotedCondRegex _ (TC_Binary _ _ "=~" _ rhs) = return . not $ hasMetachars s checkQuotedCondRegex _ _ = return () -prop_checkGlobbedRegex1 = verify checkGlobbedRegex "[[ $foo =~ *foo* ]]" -prop_checkGlobbedRegex2 = verify checkGlobbedRegex "[[ $foo =~ f* ]]" -prop_checkGlobbedRegex3 = verifyNot checkGlobbedRegex "[[ $foo =~ $foo ]]" -prop_checkGlobbedRegex4 = verifyNot checkGlobbedRegex "[[ $foo =~ ^c.* ]]" -prop_checkGlobbedRegex5 = verifyNot checkGlobbedRegex "[[ $foo =~ \\* ]]" -prop_checkGlobbedRegex6 = verifyNot checkGlobbedRegex "[[ $foo =~ (o*) ]]" -prop_checkGlobbedRegex7 = verifyNot checkGlobbedRegex "[[ $foo =~ \\*foo ]]" -prop_checkGlobbedRegex8 = verifyNot checkGlobbedRegex "[[ $foo =~ x\\* ]]" +-- | +-- >>> prop $ verify checkGlobbedRegex "[[ $foo =~ *foo* ]]" +-- >>> prop $ verify checkGlobbedRegex "[[ $foo =~ f* ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ $foo ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ ^c.* ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ \\* ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ (o*) ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ \\*foo ]]" +-- >>> prop $ verifyNot checkGlobbedRegex "[[ $foo =~ x\\* ]]" checkGlobbedRegex _ (TC_Binary _ DoubleBracket "=~" _ rhs) = let s = concat $ oversimplify rhs in when (isConfusedGlobRegex s) $ @@ -1084,16 +1110,17 @@ checkGlobbedRegex _ (TC_Binary _ DoubleBracket "=~" _ rhs) = checkGlobbedRegex _ _ = return () -prop_checkConstantIfs1 = verify checkConstantIfs "[[ foo != bar ]]" -prop_checkConstantIfs2a= verify checkConstantIfs "[ n -le 4 ]" -prop_checkConstantIfs2b= verifyNot checkConstantIfs "[[ n -le 4 ]]" -prop_checkConstantIfs3 = verify checkConstantIfs "[[ $n -le 4 && n != 2 ]]" -prop_checkConstantIfs4 = verifyNot checkConstantIfs "[[ $n -le 3 ]]" -prop_checkConstantIfs5 = verifyNot checkConstantIfs "[[ $n -le $n ]]" -prop_checkConstantIfs6 = verifyNot checkConstantIfs "[[ a -ot b ]]" -prop_checkConstantIfs7 = verifyNot checkConstantIfs "[ a -nt b ]" -prop_checkConstantIfs8 = verifyNot checkConstantIfs "[[ ~foo == '~foo' ]]" -prop_checkConstantIfs9 = 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?" @@ -1109,15 +1136,16 @@ checkConstantIfs _ (TC_Binary id typ op lhs rhs) | not isDynamic = warn id 2193 "The arguments to this comparison can never be equal. Make sure your syntax is correct." checkConstantIfs _ _ = return () -prop_checkLiteralBreakingTest = verify checkLiteralBreakingTest "[[ a==$foo ]]" -prop_checkLiteralBreakingTest2 = verify checkLiteralBreakingTest "[ $foo=3 ]" -prop_checkLiteralBreakingTest3 = verify checkLiteralBreakingTest "[ $foo!=3 ]" -prop_checkLiteralBreakingTest4 = verify checkLiteralBreakingTest "[ \"$(ls) \" ]" -prop_checkLiteralBreakingTest5 = verify checkLiteralBreakingTest "[ -n \"$(true) \" ]" -prop_checkLiteralBreakingTest6 = verify checkLiteralBreakingTest "[ -z $(true)z ]" -prop_checkLiteralBreakingTest7 = verifyNot checkLiteralBreakingTest "[ -z $(true) ]" -prop_checkLiteralBreakingTest8 = verifyNot checkLiteralBreakingTest "[ $(true)$(true) ]" -prop_checkLiteralBreakingTest10 = 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 @@ -1144,13 +1172,14 @@ checkLiteralBreakingTest _ t = potentially $ token <- listToMaybe $ filter isNonEmpty $ getWordParts t return $ err (getId token) 2157 s -prop_checkConstantNullary = verify checkConstantNullary "[[ '$(foo)' ]]" -prop_checkConstantNullary2 = verify checkConstantNullary "[ \"-f lol\" ]" -prop_checkConstantNullary3 = verify checkConstantNullary "[[ cmd ]]" -prop_checkConstantNullary4 = verify checkConstantNullary "[[ ! cmd ]]" -prop_checkConstantNullary5 = verify checkConstantNullary "[[ true ]]" -prop_checkConstantNullary6 = verify checkConstantNullary "[ 1 ]" -prop_checkConstantNullary7 = 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." @@ -1163,9 +1192,10 @@ checkConstantNullary _ (TC_Nullary _ _ t) | isConstant t = checkConstantNullary _ _ = return () -prop_checkForDecimals1 = verify checkForDecimals "((3.14*c))" -prop_checkForDecimals2 = verify checkForDecimals "foo[1.2]=bar" -prop_checkForDecimals3 = 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 @@ -1174,29 +1204,31 @@ checkForDecimals params t@(TA_Expansion id _) = potentially $ do return $ err id 2079 "(( )) doesn't support decimals. Use bc or awk." checkForDecimals _ _ = return () -prop_checkDivBeforeMult = verify checkDivBeforeMult "echo $((c/n*100))" -prop_checkDivBeforeMult2 = verifyNot checkDivBeforeMult "echo $((c*100/n))" -prop_checkDivBeforeMult3 = 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_checkArithmeticDeref = verify checkArithmeticDeref "echo $((3+$foo))" -prop_checkArithmeticDeref2 = verify checkArithmeticDeref "cow=14; (( s+= $cow ))" -prop_checkArithmeticDeref3 = verifyNot checkArithmeticDeref "cow=1/40; (( s+= ${cow%%/*} ))" -prop_checkArithmeticDeref4 = verifyNot checkArithmeticDeref "(( ! $? ))" -prop_checkArithmeticDeref5 = verifyNot checkArithmeticDeref "(($1))" -prop_checkArithmeticDeref6 = verify checkArithmeticDeref "(( a[$i] ))" -prop_checkArithmeticDeref7 = verifyNot checkArithmeticDeref "(( 10#$n ))" -prop_checkArithmeticDeref8 = verifyNot checkArithmeticDeref "let i=$i+1" -prop_checkArithmeticDeref9 = verifyNot checkArithmeticDeref "(( a[foo] ))" -prop_checkArithmeticDeref10= verifyNot checkArithmeticDeref "(( a[\\$foo] ))" -prop_checkArithmeticDeref11= verifyNot checkArithmeticDeref "a[$foo]=wee" -prop_checkArithmeticDeref12= verify checkArithmeticDeref "for ((i=0; $i < 3; i)); do true; done" -prop_checkArithmeticDeref13= verifyNot checkArithmeticDeref "(( $$ ))" -prop_checkArithmeticDeref14= verifyNot checkArithmeticDeref "(( $! ))" -prop_checkArithmeticDeref15= 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 @@ -1215,9 +1247,10 @@ checkArithmeticDeref params t@(TA_Expansion _ [b@(T_DollarBraced id _)]) = noWarning = return () checkArithmeticDeref _ _ = return () -prop_checkArithmeticBadOctal1 = verify checkArithmeticBadOctal "(( 0192 ))" -prop_checkArithmeticBadOctal2 = verifyNot checkArithmeticBadOctal "(( 0x192 ))" -prop_checkArithmeticBadOctal3 = 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 @@ -1226,12 +1259,13 @@ checkArithmeticBadOctal _ t@(TA_Expansion id _) = potentially $ do octalRE = mkRegex "^0[0-7]*[8-9]" checkArithmeticBadOctal _ _ = return () -prop_checkComparisonAgainstGlob = verify checkComparisonAgainstGlob "[[ $cow == $bar ]]" -prop_checkComparisonAgainstGlob2 = verifyNot checkComparisonAgainstGlob "[[ $cow == \"$bar\" ]]" -prop_checkComparisonAgainstGlob3 = verify checkComparisonAgainstGlob "[ $cow = *foo* ]" -prop_checkComparisonAgainstGlob4 = verifyNot checkComparisonAgainstGlob "[ $cow = foo ]" -prop_checkComparisonAgainstGlob5 = verify checkComparisonAgainstGlob "[[ $cow != $bar ]]" -prop_checkComparisonAgainstGlob6 = verify checkComparisonAgainstGlob "[ $f != /* ]" +-- | +-- >>> 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 "[ $f != /* ]" checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op _ (T_NormalWord id [T_DollarBraced _ _])) | op `elem` ["=", "==", "!="] = warn id 2053 $ "Quote the right-hand side of " ++ op ++ " in [[ ]] to prevent glob matching." @@ -1240,12 +1274,13 @@ checkComparisonAgainstGlob _ (TC_Binary _ SingleBracket op _ word) err (getId word) 2081 "[ .. ] can't match globs. Use [[ .. ]] or case statement." checkComparisonAgainstGlob _ _ = return () -prop_checkCommarrays1 = verify checkCommarrays "a=(1, 2)" -prop_checkCommarrays2 = verify checkCommarrays "a+=(1,2,3)" -prop_checkCommarrays3 = verifyNot checkCommarrays "cow=(1 \"foo,bar\" 3)" -prop_checkCommarrays4 = verifyNot checkCommarrays "cow=('one,' 'two')" -prop_checkCommarrays5 = verify checkCommarrays "a=([a]=b, [c]=d)" -prop_checkCommarrays6 = 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." @@ -1258,11 +1293,13 @@ checkCommarrays _ (T_Array id l) = isCommaSeparated str = "," `isSuffixOf` str || length (filter (== ',') str) > 1 checkCommarrays _ _ = return () -prop_checkOrNeq1 = verify checkOrNeq "if [[ $lol -ne cow || $lol -ne foo ]]; then echo foo; fi" -prop_checkOrNeq2 = verify checkOrNeq "(( a!=lol || a!=foo ))" -prop_checkOrNeq3 = verify checkOrNeq "[ \"$a\" != lol || \"$a\" != foo ]" -prop_checkOrNeq4 = verifyNot checkOrNeq "[ a != $cow || b != $foo ]" -prop_checkOrNeq5 = 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]) = @@ -1274,11 +1311,12 @@ checkOrNeq _ (TA_Binary id "||" (TA_Binary _ "!=" word1 _) (TA_Binary _ "!=" wor checkOrNeq _ _ = return () -prop_checkValidCondOps1 = verify checkValidCondOps "[[ a -xz b ]]" -prop_checkValidCondOps2 = verify checkValidCondOps "[ -M a ]" -prop_checkValidCondOps2a= verifyNot checkValidCondOps "[ 3 \\> 2 ]" -prop_checkValidCondOps3 = verifyNot checkValidCondOps "[ 1 = 2 -a 3 -ge 4 ]" -prop_checkValidCondOps4 = 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." @@ -1287,15 +1325,16 @@ checkValidCondOps _ (TC_Unary id _ s _) warn id 2058 "Unknown unary operator." checkValidCondOps _ _ = return () -prop_checkUuoeVar1 = verify checkUuoeVar "for f in $(echo $tmp); do echo lol; done" -prop_checkUuoeVar2 = verify checkUuoeVar "date +`echo \"$format\"`" -prop_checkUuoeVar3 = verifyNot checkUuoeVar "foo \"$(echo -e '\r')\"" -prop_checkUuoeVar4 = verifyNot checkUuoeVar "echo $tmp" -prop_checkUuoeVar5 = verify checkUuoeVar "foo \"$(echo \"$(date) value:\" $value)\"" -prop_checkUuoeVar6 = verifyNot checkUuoeVar "foo \"$(echo files: *.png)\"" -prop_checkUuoeVar7 = verifyNot checkUuoeVar "foo $(echo $(bar))" -- covered by 2005 -prop_checkUuoeVar8 = verifyNot checkUuoeVar "#!/bin/sh\nz=$(echo)" -prop_checkUuoeVar9 = 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 @@ -1322,10 +1361,11 @@ checkUuoeVar _ p = _ -> return () -prop_checkTestRedirects1 = verify checkTestRedirects "test 3 > 1" -prop_checkTestRedirects2 = verifyNot checkTestRedirects "test 3 \\> 1" -prop_checkTestRedirects3 = verify checkTestRedirects "/usr/bin/test $var > $foo" -prop_checkTestRedirects4 = 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 @@ -1343,16 +1383,17 @@ checkTestRedirects _ (T_Redirecting id redirs cmd) | cmd `isCommand` "test" = _ -> False checkTestRedirects _ _ = return () -prop_checkPS11 = verify checkPS1Assignments "PS1='\\033[1;35m\\$ '" -prop_checkPS11a= verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '" -prop_checkPSf2 = verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" -prop_checkPS13 = verify checkPS1Assignments "PS1=$'\\x1b[c '" -prop_checkPS14 = verify checkPS1Assignments "PS1=$'\\e[3m; '" -prop_checkPS14a= verify checkPS1Assignments "export PS1=$'\\e[3m; '" -prop_checkPS15 = verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" -prop_checkPS16 = verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" -prop_checkPS17 = verifyNot checkPS1Assignments "PS1='e033x1B'" -prop_checkPS18 = 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 = @@ -1366,20 +1407,21 @@ checkPS1Assignments _ (T_Assignment _ _ "PS1" _ word) = warnFor word escapeRegex = mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033" checkPS1Assignments _ _ = return () -prop_checkBackticks1 = verify checkBackticks "echo `foo`" -prop_checkBackticks2 = verifyNot checkBackticks "echo $(foo)" -prop_checkBackticks3 = verifyNot checkBackticks "echo `#inlined comment` foo" +-- >>> prop $ verify checkBackticks "echo `foo`" +-- >>> prop $ verifyNot checkBackticks "echo $(foo)" +-- >>> prop $ verifyNot checkBackticks "echo `#inlined comment` foo" checkBackticks params (T_Backticked id list) | not (null list) = addComment $ makeCommentWithFix StyleC id 2006 "Use $(...) notation instead of legacy backticked `...`." (fixWith [replaceStart id params 1 "$(", replaceEnd id params 1 ")"]) checkBackticks _ _ = return () -prop_checkIndirectExpansion1 = verify checkIndirectExpansion "${foo$n}" -prop_checkIndirectExpansion2 = verifyNot checkIndirectExpansion "${foo//$n/lol}" -prop_checkIndirectExpansion3 = verify checkIndirectExpansion "${$#}" -prop_checkIndirectExpansion4 = verify checkIndirectExpansion "${var${n}_$((i%2))}" -prop_checkIndirectExpansion5 = 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." @@ -1399,14 +1441,15 @@ checkIndirectExpansion _ (T_DollarBraced i (T_NormalWord _ contents)) = checkIndirectExpansion _ _ = return () -prop_checkInexplicablyUnquoted1 = verify checkInexplicablyUnquoted "echo 'var='value';'" -prop_checkInexplicablyUnquoted2 = verifyNot checkInexplicablyUnquoted "'foo'*" -prop_checkInexplicablyUnquoted3 = verifyNot checkInexplicablyUnquoted "wget --user-agent='something'" -prop_checkInexplicablyUnquoted4 = verify checkInexplicablyUnquoted "echo \"VALUES (\"id\")\"" -prop_checkInexplicablyUnquoted5 = verifyNot checkInexplicablyUnquoted "\"$dir\"/\"$file\"" -prop_checkInexplicablyUnquoted6 = verifyNot checkInexplicablyUnquoted "\"$dir\"some_stuff\"$file\"" -prop_checkInexplicablyUnquoted7 = verifyNot checkInexplicablyUnquoted "${dir/\"foo\"/\"bar\"}" -prop_checkInexplicablyUnquoted8 = 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:_) @@ -1438,11 +1481,12 @@ checkInexplicablyUnquoted _ (T_NormalWord id tokens) = mapM_ check (tails tokens warn id 2140 "Word is of the form \"A\"B\"C\" (B indicated). Did you mean \"ABC\" or \"A\\\"B\\\"C\"?" checkInexplicablyUnquoted _ _ = return () -prop_checkTildeInQuotes1 = verify checkTildeInQuotes "var=\"~/out.txt\"" -prop_checkTildeInQuotes2 = verify checkTildeInQuotes "foo > '~/dir'" -prop_checkTildeInQuotes4 = verifyNot checkTildeInQuotes "~/file" -prop_checkTildeInQuotes5 = verifyNot checkTildeInQuotes "echo '/~foo/cow'" -prop_checkTildeInQuotes6 = 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." @@ -1453,22 +1497,23 @@ checkTildeInQuotes _ = check verify id str check _ = return () -prop_checkLonelyDotDash1 = verify checkLonelyDotDash "./ file" -prop_checkLonelyDotDash2 = 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'." checkLonelyDotDash _ _ = return () -prop_checkSpuriousExec1 = verify checkSpuriousExec "exec foo; true" -prop_checkSpuriousExec2 = verify checkSpuriousExec "if a; then exec b; exec c; fi" -prop_checkSpuriousExec3 = verifyNot checkSpuriousExec "echo cow; exec foo" -prop_checkSpuriousExec4 = verifyNot checkSpuriousExec "if a; then exec b; fi" -prop_checkSpuriousExec5 = verifyNot checkSpuriousExec "exec > file; cmd" -prop_checkSpuriousExec6 = verify checkSpuriousExec "exec foo > file; cmd" -prop_checkSpuriousExec7 = verifyNot checkSpuriousExec "exec file; echo failed; exit 3" -prop_checkSpuriousExec8 = 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 @@ -1503,10 +1548,11 @@ checkSpuriousExec _ = doLists commentIfExec _ = return () -prop_checkSpuriousExpansion1 = verify checkSpuriousExpansion "if $(true); then true; fi" -prop_checkSpuriousExpansion2 = verify checkSpuriousExpansion "while \"$(cmd)\"; do :; done" -prop_checkSpuriousExpansion3 = verifyNot checkSpuriousExpansion "$(cmd) --flag1 --flag2" -prop_checkSpuriousExpansion4 = 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 @@ -1521,14 +1567,16 @@ checkSpuriousExpansion _ (T_SimpleCommand _ _ [T_NormalWord _ [word]]) = check w checkSpuriousExpansion _ _ = return () -prop_checkDollarBrackets1 = verify checkDollarBrackets "echo $[1+2]" -prop_checkDollarBrackets2 = 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_checkSshHereDoc1 = verify checkSshHereDoc "ssh host << foo\necho $PATH\nfoo" -prop_checkSshHereDoc2 = 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 @@ -1540,27 +1588,28 @@ checkSshHereDoc _ (T_Redirecting _ redirs cmd) checkHereDoc _ = return () checkSshHereDoc _ _ = return () ---- Subshell detection -prop_subshellAssignmentCheck = verifyTree subshellAssignmentCheck "cat foo | while read bar; do a=$bar; done; echo \"$a\"" -prop_subshellAssignmentCheck2 = verifyNotTree subshellAssignmentCheck "while read bar; do a=$bar; done < file; echo \"$a\"" -prop_subshellAssignmentCheck3 = verifyTree subshellAssignmentCheck "( A=foo; ); rm $A" -prop_subshellAssignmentCheck4 = verifyNotTree subshellAssignmentCheck "( A=foo; rm $A; )" -prop_subshellAssignmentCheck5 = verifyTree subshellAssignmentCheck "cat foo | while read cow; do true; done; echo $cow;" -prop_subshellAssignmentCheck6 = verifyTree subshellAssignmentCheck "( export lol=$(ls); ); echo $lol;" -prop_subshellAssignmentCheck6a= verifyTree subshellAssignmentCheck "( typeset -a lol=a; ); echo $lol;" -prop_subshellAssignmentCheck7 = verifyTree subshellAssignmentCheck "cmd | while read foo; do (( n++ )); done; echo \"$n lines\"" -prop_subshellAssignmentCheck8 = verifyTree subshellAssignmentCheck "n=3 & echo $((n++))" -prop_subshellAssignmentCheck9 = verifyTree subshellAssignmentCheck "read n & n=foo$n" -prop_subshellAssignmentCheck10 = verifyTree subshellAssignmentCheck "(( n <<= 3 )) & (( n |= 4 )) &" -prop_subshellAssignmentCheck11 = verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let n=n+1; done\necho $n" -prop_subshellAssignmentCheck12 = verifyTree subshellAssignmentCheck "cat /etc/passwd | while read line; do let ++n; done\necho $n" -prop_subshellAssignmentCheck13 = verifyTree subshellAssignmentCheck "#!/bin/bash\necho foo | read bar; echo $bar" -prop_subshellAssignmentCheck14 = verifyNotTree subshellAssignmentCheck "#!/bin/ksh93\necho foo | read bar; echo $bar" -prop_subshellAssignmentCheck15 = verifyNotTree subshellAssignmentCheck "#!/bin/ksh\ncat foo | while read bar; do a=$bar; done\necho \"$a\"" -prop_subshellAssignmentCheck16 = verifyNotTree subshellAssignmentCheck "(set -e); echo $@" -prop_subshellAssignmentCheck17 = verifyNotTree subshellAssignmentCheck "foo=${ { bar=$(baz); } 2>&1; }; echo $foo $bar" -prop_subshellAssignmentCheck18 = verifyTree subshellAssignmentCheck "( exec {n}>&2; ); echo $n" -prop_subshellAssignmentCheck19 = verifyNotTree subshellAssignmentCheck "#!/bin/bash\nshopt -s lastpipe; echo a | read -r b; echo \"$b\"" +-- | 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\"" subshellAssignmentCheck params t = let flow = variableFlow params check = findSubshelled flow [("oops",[])] Map.empty @@ -1610,43 +1659,44 @@ doVariableFlowAnalysis readFunc writeFunc empty flow = evalState ( writeFunc base token name values doFlow _ = return [] ----- Check whether variables could have spaces/globs -prop_checkSpacefulness1 = verifyTree checkSpacefulness "a='cow moo'; echo $a" -prop_checkSpacefulness2 = verifyNotTree checkSpacefulness "a='cow moo'; [[ $a ]]" -prop_checkSpacefulness3 = verifyNotTree checkSpacefulness "a='cow*.mp3'; echo \"$a\"" -prop_checkSpacefulness4 = verifyTree checkSpacefulness "for f in *.mp3; do echo $f; done" -prop_checkSpacefulness4a= verifyNotTree checkSpacefulness "foo=3; foo=$(echo $foo)" -prop_checkSpacefulness5 = verifyTree checkSpacefulness "a='*'; b=$a; c=lol${b//foo/bar}; echo $c" -prop_checkSpacefulness6 = verifyTree checkSpacefulness "a=foo$(lol); echo $a" -prop_checkSpacefulness7 = verifyTree checkSpacefulness "a=foo\\ bar; rm $a" -prop_checkSpacefulness8 = verifyNotTree checkSpacefulness "a=foo\\ bar; a=foo; rm $a" -prop_checkSpacefulness10= verifyTree checkSpacefulness "rm $1" -prop_checkSpacefulness11= verifyTree checkSpacefulness "rm ${10//foo/bar}" -prop_checkSpacefulness12= verifyNotTree checkSpacefulness "(( $1 + 3 ))" -prop_checkSpacefulness13= verifyNotTree checkSpacefulness "if [[ $2 -gt 14 ]]; then true; fi" -prop_checkSpacefulness14= verifyNotTree checkSpacefulness "foo=$3 env" -prop_checkSpacefulness15= verifyNotTree checkSpacefulness "local foo=$1" -prop_checkSpacefulness16= verifyNotTree checkSpacefulness "declare foo=$1" -prop_checkSpacefulness17= verifyTree checkSpacefulness "echo foo=$1" -prop_checkSpacefulness18= verifyNotTree checkSpacefulness "$1 --flags" -prop_checkSpacefulness19= verifyTree checkSpacefulness "echo $PWD" -prop_checkSpacefulness20= verifyNotTree checkSpacefulness "n+='foo bar'" -prop_checkSpacefulness21= verifyNotTree checkSpacefulness "select foo in $bar; do true; done" -prop_checkSpacefulness22= verifyNotTree checkSpacefulness "echo $\"$1\"" -prop_checkSpacefulness23= verifyNotTree checkSpacefulness "a=(1); echo ${a[@]}" -prop_checkSpacefulness24= verifyTree checkSpacefulness "a='a b'; cat <<< $a" -prop_checkSpacefulness25= verifyTree checkSpacefulness "a='s/[0-9]//g'; sed $a" -prop_checkSpacefulness26= verifyTree checkSpacefulness "a='foo bar'; echo {1,2,$a}" -prop_checkSpacefulness27= verifyNotTree checkSpacefulness "echo ${a:+'foo'}" -prop_checkSpacefulness28= verifyNotTree checkSpacefulness "exec {n}>&1; echo $n" -prop_checkSpacefulness29= verifyNotTree checkSpacefulness "n=$(stuff); exec {n}>&-;" -prop_checkSpacefulness30= verifyTree checkSpacefulness "file='foo bar'; echo foo > $file;" -prop_checkSpacefulness31= verifyNotTree checkSpacefulness "echo \"`echo \\\"$1\\\"`\"" -prop_checkSpacefulness32= verifyNotTree checkSpacefulness "var=$1; [ -v var ]" -prop_checkSpacefulness33= verifyTree checkSpacefulness "for file; do echo $file; done" -prop_checkSpacefulness34= verifyTree checkSpacefulness "declare foo$n=$1" -prop_checkSpacefulness35= verifyNotTree checkSpacefulness "echo ${1+\"$1\"}" -prop_checkSpacefulness36= verifyNotTree checkSpacefulness "arg=$#; echo $arg" +-- | 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 $ verifyNotTree checkSpacefulness "arg=$#; echo $arg" checkSpacefulness params t = doVariableFlowAnalysis readF writeF (Map.fromList defaults) (variableFlow params) @@ -1724,17 +1774,18 @@ checkSpacefulness params t = && any (`isPrefixOf` modifier) ["=", ":="] && isParamTo parents ":" token -prop_checkQuotesInLiterals1 = verifyTree checkQuotesInLiterals "param='--foo=\"bar\"'; app $param" -prop_checkQuotesInLiterals1a= verifyTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; app $param" -prop_checkQuotesInLiterals2 = verifyNotTree checkQuotesInLiterals "param='--foo=\"bar\"'; app \"$param\"" -prop_checkQuotesInLiterals3 =verifyNotTree checkQuotesInLiterals "param=('--foo='); app \"${param[@]}\"" -prop_checkQuotesInLiterals4 = verifyNotTree checkQuotesInLiterals "param=\"don't bother with this one\"; app $param" -prop_checkQuotesInLiterals5 = verifyNotTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; eval app $param" -prop_checkQuotesInLiterals6 = verifyTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm $param\"; $cmd" -prop_checkQuotesInLiterals6a= verifyNotTree checkQuotesInLiterals "param='my\\ file'; cmd=\"rm ${#param}\"; $cmd" -prop_checkQuotesInLiterals7 = verifyTree checkQuotesInLiterals "param='my\\ file'; rm $param" -prop_checkQuotesInLiterals8 = verifyTree checkQuotesInLiterals "param=\"/foo/'bar baz'/etc\"; rm $param" -prop_checkQuotesInLiterals9 = 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 @@ -1787,14 +1838,11 @@ checkQuotesInLiterals params t = else []) -prop_checkFunctionsUsedExternally1 = - verifyTree checkFunctionsUsedExternally "foo() { :; }; sudo foo" -prop_checkFunctionsUsedExternally2 = - verifyTree checkFunctionsUsedExternally "alias f='a'; xargs -n 1 f" -prop_checkFunctionsUsedExternally3 = - verifyNotTree checkFunctionsUsedExternally "f() { :; }; echo f" -prop_checkFunctionsUsedExternally4 = - 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 @@ -1832,47 +1880,48 @@ checkFunctionsUsedExternally params t = info definitionId 2032 $ "Use own script or sh -c '..' to run this from " ++ cmd ++ "." -prop_checkUnused0 = verifyNotTree checkUnusedAssignments "var=foo; echo $var" -prop_checkUnused1 = verifyTree checkUnusedAssignments "var=foo; echo $bar" -prop_checkUnused2 = verifyNotTree checkUnusedAssignments "var=foo; export var;" -prop_checkUnused3 = verifyTree checkUnusedAssignments "for f in *; do echo '$f'; done" -prop_checkUnused4 = verifyTree checkUnusedAssignments "local i=0" -prop_checkUnused5 = verifyNotTree checkUnusedAssignments "read lol; echo $lol" -prop_checkUnused6 = verifyNotTree checkUnusedAssignments "var=4; (( var++ ))" -prop_checkUnused7 = verifyNotTree checkUnusedAssignments "var=2; $((var))" -prop_checkUnused8 = verifyTree checkUnusedAssignments "var=2; var=3;" -prop_checkUnused9 = verifyNotTree checkUnusedAssignments "read ''" -prop_checkUnused10= verifyNotTree checkUnusedAssignments "read -p 'test: '" -prop_checkUnused11= verifyNotTree checkUnusedAssignments "bar=5; export foo[$bar]=3" -prop_checkUnused12= verifyNotTree checkUnusedAssignments "read foo; echo ${!foo}" -prop_checkUnused13= verifyNotTree checkUnusedAssignments "x=(1); (( x[0] ))" -prop_checkUnused14= verifyNotTree checkUnusedAssignments "x=(1); n=0; echo ${x[n]}" -prop_checkUnused15= verifyNotTree checkUnusedAssignments "x=(1); n=0; (( x[n] ))" -prop_checkUnused16= verifyNotTree checkUnusedAssignments "foo=5; declare -x foo" -prop_checkUnused17= verifyNotTree checkUnusedAssignments "read -i 'foo' -e -p 'Input: ' bar; $bar;" -prop_checkUnused18= verifyNotTree checkUnusedAssignments "a=1; arr=( [$a]=42 ); echo \"${arr[@]}\"" -prop_checkUnused19= verifyNotTree checkUnusedAssignments "a=1; let b=a+1; echo $b" -prop_checkUnused20= verifyNotTree checkUnusedAssignments "a=1; PS1='$a'" -prop_checkUnused21= verifyNotTree checkUnusedAssignments "a=1; trap 'echo $a' INT" -prop_checkUnused22= verifyNotTree checkUnusedAssignments "a=1; [ -v a ]" -prop_checkUnused23= verifyNotTree checkUnusedAssignments "a=1; [ -R a ]" -prop_checkUnused24= verifyNotTree checkUnusedAssignments "mapfile -C a b; echo ${b[@]}" -prop_checkUnused25= verifyNotTree checkUnusedAssignments "readarray foo; echo ${foo[@]}" -prop_checkUnused26= verifyNotTree checkUnusedAssignments "declare -F foo" -prop_checkUnused27= verifyTree checkUnusedAssignments "var=3; [ var -eq 3 ]" -prop_checkUnused28= verifyNotTree checkUnusedAssignments "var=3; [[ var -eq 3 ]]" -prop_checkUnused29= verifyNotTree checkUnusedAssignments "var=(a b); declare -p var" -prop_checkUnused30= verifyTree checkUnusedAssignments "let a=1" -prop_checkUnused31= verifyTree checkUnusedAssignments "let 'a=1'" -prop_checkUnused32= verifyTree checkUnusedAssignments "let a=b=c; echo $a" -prop_checkUnused33= verifyNotTree checkUnusedAssignments "a=foo; [[ foo =~ ^{$a}$ ]]" -prop_checkUnused34= verifyNotTree checkUnusedAssignments "foo=1; (( t = foo )); echo $t" -prop_checkUnused35= verifyNotTree checkUnusedAssignments "a=foo; b=2; echo ${a:b}" -prop_checkUnused36= verifyNotTree checkUnusedAssignments "if [[ -v foo ]]; then true; fi" -prop_checkUnused37= verifyNotTree checkUnusedAssignments "fd=2; exec {fd}>&-" -prop_checkUnused38= verifyTree checkUnusedAssignments "(( a=42 ))" -prop_checkUnused39= verifyNotTree checkUnusedAssignments "declare -x -f foo" -prop_checkUnused40= 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 @@ -1895,42 +1944,43 @@ checkUnusedAssignments params t = execWriter (mapM_ warnFor unused) stripSuffix = takeWhile isVariableChar defaultMap = Map.fromList $ zip internalVariables $ repeat () -prop_checkUnassignedReferences1 = verifyTree checkUnassignedReferences "echo $foo" -prop_checkUnassignedReferences2 = verifyNotTree checkUnassignedReferences "foo=hello; echo $foo" -prop_checkUnassignedReferences3 = verifyTree checkUnassignedReferences "MY_VALUE=3; echo $MYVALUE" -prop_checkUnassignedReferences4 = verifyNotTree checkUnassignedReferences "RANDOM2=foo; echo $RANDOM" -prop_checkUnassignedReferences5 = verifyNotTree checkUnassignedReferences "declare -A foo=([bar]=baz); echo ${foo[bar]}" -prop_checkUnassignedReferences6 = verifyNotTree checkUnassignedReferences "foo=..; echo ${foo-bar}" -prop_checkUnassignedReferences7 = verifyNotTree checkUnassignedReferences "getopts ':h' foo; echo $foo" -prop_checkUnassignedReferences8 = verifyNotTree checkUnassignedReferences "let 'foo = 1'; echo $foo" -prop_checkUnassignedReferences9 = verifyNotTree checkUnassignedReferences "echo ${foo-bar}" -prop_checkUnassignedReferences10= verifyNotTree checkUnassignedReferences "echo ${foo:?}" -prop_checkUnassignedReferences11= verifyNotTree checkUnassignedReferences "declare -A foo; echo \"${foo[@]}\"" -prop_checkUnassignedReferences12= verifyNotTree checkUnassignedReferences "typeset -a foo; echo \"${foo[@]}\"" -prop_checkUnassignedReferences13= verifyNotTree checkUnassignedReferences "f() { local foo; echo $foo; }" -prop_checkUnassignedReferences14= verifyNotTree checkUnassignedReferences "foo=; echo $foo" -prop_checkUnassignedReferences15= verifyNotTree checkUnassignedReferences "f() { true; }; export -f f" -prop_checkUnassignedReferences16= verifyNotTree checkUnassignedReferences "declare -A foo=( [a b]=bar ); echo ${foo[a b]}" -prop_checkUnassignedReferences17= verifyNotTree checkUnassignedReferences "USERS=foo; echo $USER" -prop_checkUnassignedReferences18= verifyNotTree checkUnassignedReferences "FOOBAR=42; export FOOBAR=" -prop_checkUnassignedReferences19= verifyNotTree checkUnassignedReferences "readonly foo=bar; echo $foo" -prop_checkUnassignedReferences20= verifyNotTree checkUnassignedReferences "printf -v foo bar; echo $foo" -prop_checkUnassignedReferences21= verifyTree checkUnassignedReferences "echo ${#foo}" -prop_checkUnassignedReferences22= verifyNotTree checkUnassignedReferences "echo ${!os*}" -prop_checkUnassignedReferences23= verifyTree checkUnassignedReferences "declare -a foo; foo[bar]=42;" -prop_checkUnassignedReferences24= verifyNotTree checkUnassignedReferences "declare -A foo; foo[bar]=42;" -prop_checkUnassignedReferences25= verifyNotTree checkUnassignedReferences "declare -A foo=(); foo[bar]=42;" -prop_checkUnassignedReferences26= verifyNotTree checkUnassignedReferences "a::b() { foo; }; readonly -f a::b" -prop_checkUnassignedReferences27= verifyNotTree checkUnassignedReferences ": ${foo:=bar}" -prop_checkUnassignedReferences28= verifyNotTree checkUnassignedReferences "#!/bin/ksh\necho \"${.sh.version}\"\n" -prop_checkUnassignedReferences29= verifyNotTree checkUnassignedReferences "if [[ -v foo ]]; then echo $foo; fi" -prop_checkUnassignedReferences30= verifyNotTree checkUnassignedReferences "if [[ -v foo[3] ]]; then echo ${foo[3]}; fi" -prop_checkUnassignedReferences31= verifyNotTree checkUnassignedReferences "X=1; if [[ -v foo[$X+42] ]]; then echo ${foo[$X+42]}; fi" -prop_checkUnassignedReferences32= verifyNotTree checkUnassignedReferences "if [[ -v \"foo[1]\" ]]; then echo ${foo[@]}; fi" -prop_checkUnassignedReferences33= verifyNotTree checkUnassignedReferences "f() { local -A foo; echo \"${foo[@]}\"; }" -prop_checkUnassignedReferences34= verifyNotTree checkUnassignedReferences "declare -A foo; (( foo[bar] ))" -prop_checkUnassignedReferences35= verifyNotTree checkUnassignedReferences "echo ${arr[foo-bar]:?fail}" -prop_checkUnassignedReferences36= verifyNotTree checkUnassignedReferences "read -a foo -r <<<\"foo bar\"; echo \"$foo\"" +-- | +-- >>> 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 $ verifyNotTree checkUnassignedReferences "read -a foo -r <<<\"foo bar\"; echo \"$foo\"" checkUnassignedReferences params t = warnings where (readMap, writeMap) = execState (mapM tally $ variableFlow params) (Map.empty, Map.empty) @@ -2004,10 +2054,11 @@ checkUnassignedReferences params t = warnings else dist var candidate -prop_checkGlobsAsOptions1 = verify checkGlobsAsOptions "rm *.txt" -prop_checkGlobsAsOptions2 = verify checkGlobsAsOptions "ls ??.*" -prop_checkGlobsAsOptions3 = verifyNot checkGlobsAsOptions "rm -- *.txt" -prop_checkGlobsAsOptions4 = 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 +2076,15 @@ checkGlobsAsOptions _ (T_SimpleCommand _ _ args) = checkGlobsAsOptions _ _ = return () -prop_checkWhileReadPitfalls1 = verify checkWhileReadPitfalls "while read foo; do ssh $foo uptime; done < file" -prop_checkWhileReadPitfalls2 = verifyNot checkWhileReadPitfalls "while read -u 3 foo; do ssh $foo uptime; done 3< file" -prop_checkWhileReadPitfalls3 = verifyNot checkWhileReadPitfalls "while true; do ssh host uptime; done" -prop_checkWhileReadPitfalls4 = verifyNot checkWhileReadPitfalls "while read foo; do ssh $foo hostname < /dev/null; done" -prop_checkWhileReadPitfalls5 = verifyNot checkWhileReadPitfalls "while read foo; do echo ls | ssh $foo; done" -prop_checkWhileReadPitfalls6 = verifyNot checkWhileReadPitfalls "while read foo <&3; do ssh $foo; done 3< foo" -prop_checkWhileReadPitfalls7 = verify checkWhileReadPitfalls "while read foo; do if true; then ssh $foo uptime; fi; done < file" -prop_checkWhileReadPitfalls8 = 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 = @@ -2073,8 +2125,9 @@ checkWhileReadPitfalls _ (T_WhileExpression id [command] contents) checkWhileReadPitfalls _ _ = return () -prop_checkPrefixAssign1 = verify checkPrefixAssignmentReference "var=foo echo $var" -prop_checkPrefixAssign2 = 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 @@ -2095,11 +2148,12 @@ checkPrefixAssignmentReference params t@(T_DollarBraced id value) = checkPrefixAssignmentReference _ _ = return () -prop_checkCharRangeGlob1 = verify checkCharRangeGlob "ls *[:digit:].jpg" -prop_checkCharRangeGlob2 = verifyNot checkCharRangeGlob "ls *[[:digit:]].jpg" -prop_checkCharRangeGlob3 = verify checkCharRangeGlob "ls [10-15]" -prop_checkCharRangeGlob4 = verifyNot checkCharRangeGlob "ls [a-zA-Z]" -prop_checkCharRangeGlob5 = 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 @@ -2116,12 +2170,12 @@ checkCharRangeGlob p t@(T_Glob id str) | checkCharRangeGlob _ _ = return () - -prop_checkCdAndBack1 = verify checkCdAndBack "for f in *; do cd $f; git pull; cd ..; done" -prop_checkCdAndBack2 = verifyNot checkCdAndBack "for f in *; do cd $f || continue; git pull; cd ..; done" -prop_checkCdAndBack3 = verifyNot checkCdAndBack "while [[ $PWD != / ]]; do cd ..; done" -prop_checkCdAndBack4 = verify checkCdAndBack "cd $tmp; foo; cd -" -prop_checkCdAndBack5 = verifyNot checkCdAndBack "cd ..; 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 -" +-- >>> prop $ verifyNot checkCdAndBack "cd ..; foo; cd .." checkCdAndBack params = doLists where shell = shellType params @@ -2161,13 +2215,14 @@ checkCdAndBack params = doLists message = "Use a ( subshell ) to avoid having to cd back." -prop_checkLoopKeywordScope1 = verify checkLoopKeywordScope "continue 2" -prop_checkLoopKeywordScope2 = verify checkLoopKeywordScope "for f; do ( break; ); done" -prop_checkLoopKeywordScope3 = verify checkLoopKeywordScope "if true; then continue; fi" -prop_checkLoopKeywordScope4 = verifyNot checkLoopKeywordScope "while true; do break; done" -prop_checkLoopKeywordScope5 = verify checkLoopKeywordScope "if true; then break; fi" -prop_checkLoopKeywordScope6 = verify checkLoopKeywordScope "while true; do true | { break; }; done" -prop_checkLoopKeywordScope7 = 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 @@ -2189,9 +2244,10 @@ checkLoopKeywordScope params t | checkLoopKeywordScope _ _ = return () -prop_checkFunctionDeclarations1 = verify checkFunctionDeclarations "#!/bin/ksh\nfunction foo() { command foo --lol \"$@\"; }" -prop_checkFunctionDeclarations2 = verify checkFunctionDeclarations "#!/bin/dash\nfunction foo { lol; }" -prop_checkFunctionDeclarations3 = 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 @@ -2212,8 +2268,9 @@ checkFunctionDeclarations _ _ = return () -prop_checkStderrPipe1 = verify checkStderrPipe "#!/bin/ksh\nfoo |& bar" -prop_checkStderrPipe2 = 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 @@ -2223,18 +2280,19 @@ checkStderrPipe params = err id 2118 "Ksh does not support |&. Use 2>&1 |." match _ = return () -prop_checkUnpassedInFunctions1 = verifyTree checkUnpassedInFunctions "foo() { echo $1; }; foo" -prop_checkUnpassedInFunctions2 = verifyNotTree checkUnpassedInFunctions "foo() { echo $1; };" -prop_checkUnpassedInFunctions3 = verifyNotTree checkUnpassedInFunctions "foo() { echo $lol; }; foo" -prop_checkUnpassedInFunctions4 = verifyNotTree checkUnpassedInFunctions "foo() { echo $0; }; foo" -prop_checkUnpassedInFunctions5 = verifyNotTree checkUnpassedInFunctions "foo() { echo $1; }; foo 'lol'; foo" -prop_checkUnpassedInFunctions6 = verifyNotTree checkUnpassedInFunctions "foo() { set -- *; echo $1; }; foo" -prop_checkUnpassedInFunctions7 = verifyTree checkUnpassedInFunctions "foo() { echo $1; }; foo; foo;" -prop_checkUnpassedInFunctions8 = verifyNotTree checkUnpassedInFunctions "foo() { echo $((1)); }; foo;" -prop_checkUnpassedInFunctions9 = verifyNotTree checkUnpassedInFunctions "foo() { echo $(($b)); }; foo;" -prop_checkUnpassedInFunctions10= verifyNotTree checkUnpassedInFunctions "foo() { echo $!; }; foo;" -prop_checkUnpassedInFunctions11= verifyNotTree checkUnpassedInFunctions "foo() { bar() { echo $1; }; bar baz; }; foo;" -prop_checkUnpassedInFunctions12= 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 @@ -2298,14 +2356,15 @@ checkUnpassedInFunctions params root = name ++ " references arguments, but none are ever passed." -prop_checkOverridingPath1 = verify checkOverridingPath "PATH=\"$var/$foo\"" -prop_checkOverridingPath2 = verify checkOverridingPath "PATH=\"mydir\"" -prop_checkOverridingPath3 = verify checkOverridingPath "PATH=/cow/foo" -prop_checkOverridingPath4 = verifyNot checkOverridingPath "PATH=/cow/foo/bin" -prop_checkOverridingPath5 = verifyNot checkOverridingPath "PATH='/bin:/sbin'" -prop_checkOverridingPath6 = verifyNot checkOverridingPath "PATH=\"$var/$foo\" cmd" -prop_checkOverridingPath7 = verifyNot checkOverridingPath "PATH=$OLDPATH" -prop_checkOverridingPath8 = 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 @@ -2318,9 +2377,10 @@ checkOverridingPath _ (T_SimpleCommand _ vars []) = notify id = warn id 2123 "PATH is the shell search path. Use another name." checkOverridingPath _ _ = return () -prop_checkTildeInPath1 = verify checkTildeInPath "PATH=\"$PATH:~/bin\"" -prop_checkTildeInPath2 = verify checkTildeInPath "PATH='~foo/bin'" -prop_checkTildeInPath3 = 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 @@ -2335,9 +2395,10 @@ checkTildeInPath _ (T_SimpleCommand _ vars _) = isQuoted _ = False checkTildeInPath _ _ = return () -prop_checkUnsupported3 = verify checkUnsupported "#!/bin/sh\ncase foo in bar) baz ;& esac" -prop_checkUnsupported4 = verify checkUnsupported "#!/bin/ksh\ncase foo in bar) baz ;;& esac" -prop_checkUnsupported5 = 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 +2422,10 @@ shellSupport t = groupWith f = groupBy ((==) `on` f) -prop_checkMultipleAppends1 = verify checkMultipleAppends "foo >> file; bar >> file; baz >> file;" -prop_checkMultipleAppends2 = verify checkMultipleAppends "foo >> file; bar | grep f >> file; baz >> file;" -prop_checkMultipleAppends3 = 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 @@ -2383,8 +2445,9 @@ checkMultipleAppends params t = getAppend _ = Nothing -prop_checkSuspiciousIFS1 = verify checkSuspiciousIFS "IFS=\"\\n\"" -prop_checkSuspiciousIFS2 = 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 @@ -2403,12 +2466,13 @@ checkSuspiciousIFS params (T_Assignment id Assign "IFS" [] value) = checkSuspiciousIFS _ _ = return () -prop_checkGrepQ1= verify checkShouldUseGrepQ "[[ $(foo | grep bar) ]]" -prop_checkGrepQ2= verify checkShouldUseGrepQ "[ -z $(fgrep lol) ]" -prop_checkGrepQ3= verify checkShouldUseGrepQ "[ -n \"$(foo | zgrep lol)\" ]" -prop_checkGrepQ4= verifyNot checkShouldUseGrepQ "[ -z $(grep bar | cmd) ]" -prop_checkGrepQ5= verifyNot checkShouldUseGrepQ "rm $(ls | grep file)" -prop_checkGrepQ6= 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 @@ -2439,22 +2503,23 @@ checkShouldUseGrepQ params t = _ -> fail "unknown" isGrep = (`elem` ["grep", "egrep", "fgrep", "zgrep"]) -prop_checkTestArgumentSplitting1 = verify checkTestArgumentSplitting "[ -e *.mp3 ]" -prop_checkTestArgumentSplitting2 = verifyNot checkTestArgumentSplitting "[[ $a == *b* ]]" -prop_checkTestArgumentSplitting3 = verify checkTestArgumentSplitting "[[ *.png == '' ]]" -prop_checkTestArgumentSplitting4 = verify checkTestArgumentSplitting "[[ foo == f{o,oo,ooo} ]]" -prop_checkTestArgumentSplitting5 = verify checkTestArgumentSplitting "[[ $@ ]]" -prop_checkTestArgumentSplitting6 = verify checkTestArgumentSplitting "[ -e $@ ]" -prop_checkTestArgumentSplitting7 = verify checkTestArgumentSplitting "[ $@ == $@ ]" -prop_checkTestArgumentSplitting8 = verify checkTestArgumentSplitting "[[ $@ = $@ ]]" -prop_checkTestArgumentSplitting9 = verifyNot checkTestArgumentSplitting "[[ foo =~ bar{1,2} ]]" -prop_checkTestArgumentSplitting10 = verifyNot checkTestArgumentSplitting "[ \"$@\" ]" -prop_checkTestArgumentSplitting11 = verify checkTestArgumentSplitting "[[ \"$@\" ]]" -prop_checkTestArgumentSplitting12 = verify checkTestArgumentSplitting "[ *.png ]" -prop_checkTestArgumentSplitting13 = verify checkTestArgumentSplitting "[ \"$@\" == \"\" ]" -prop_checkTestArgumentSplitting14 = verify checkTestArgumentSplitting "[[ \"$@\" == \"\" ]]" -prop_checkTestArgumentSplitting15 = verifyNot checkTestArgumentSplitting "[[ \"$*\" == \"\" ]]" -prop_checkTestArgumentSplitting16 = 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 @@ -2509,11 +2574,12 @@ checkTestArgumentSplitting _ t = else err (getId token) 2203 "Globs are ignored in [[ ]] except right of =/!=. Use a loop." -prop_checkMaskedReturns1 = verify checkMaskedReturns "f() { local a=$(false); }" -prop_checkMaskedReturns2 = verify checkMaskedReturns "declare a=$(false)" -prop_checkMaskedReturns3 = verify checkMaskedReturns "declare a=\"`false`\"" -prop_checkMaskedReturns4 = verifyNot checkMaskedReturns "declare a; a=$(false)" -prop_checkMaskedReturns5 = 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"] @@ -2531,40 +2597,42 @@ checkMaskedReturns _ t@(T_SimpleCommand id _ (cmd:rest)) = potentially $ do checkMaskedReturns _ _ = return () -prop_checkReadWithoutR1 = verify checkReadWithoutR "read -a foo" -prop_checkReadWithoutR2 = 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_checkUncheckedCd1 = verifyTree checkUncheckedCdPushdPopd "cd ~/src; rm -r foo" -prop_checkUncheckedCd2 = verifyNotTree checkUncheckedCdPushdPopd "cd ~/src || exit; rm -r foo" -prop_checkUncheckedCd3 = verifyNotTree checkUncheckedCdPushdPopd "set -e; cd ~/src; rm -r foo" -prop_checkUncheckedCd4 = verifyNotTree checkUncheckedCdPushdPopd "if cd foo; then rm foo; fi" -prop_checkUncheckedCd5 = verifyTree checkUncheckedCdPushdPopd "if true; then cd foo; fi" -prop_checkUncheckedCd6 = verifyNotTree checkUncheckedCdPushdPopd "cd .." -prop_checkUncheckedCd7 = verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\ncd foo\nrm bar" -prop_checkUncheckedCd8 = verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; cd foo; rm bar" -prop_checkUncheckedCd9 = verifyTree checkUncheckedCdPushdPopd "builtin cd ~/src; rm -r foo" -prop_checkUncheckedPushd1 = verifyTree checkUncheckedCdPushdPopd "pushd ~/src; rm -r foo" -prop_checkUncheckedPushd2 = verifyNotTree checkUncheckedCdPushdPopd "pushd ~/src || exit; rm -r foo" -prop_checkUncheckedPushd3 = verifyNotTree checkUncheckedCdPushdPopd "set -e; pushd ~/src; rm -r foo" -prop_checkUncheckedPushd4 = verifyNotTree checkUncheckedCdPushdPopd "if pushd foo; then rm foo; fi" -prop_checkUncheckedPushd5 = verifyTree checkUncheckedCdPushdPopd "if true; then pushd foo; fi" -prop_checkUncheckedPushd6 = verifyNotTree checkUncheckedCdPushdPopd "pushd .." -prop_checkUncheckedPushd7 = verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\npushd foo\nrm bar" -prop_checkUncheckedPushd8 = verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; pushd foo; rm bar" -prop_checkUncheckedPushd9 = verifyNotTree checkUncheckedCdPushdPopd "pushd -n foo" -prop_checkUncheckedPopd1 = verifyTree checkUncheckedCdPushdPopd "popd; rm -r foo" -prop_checkUncheckedPopd2 = verifyNotTree checkUncheckedCdPushdPopd "popd || exit; rm -r foo" -prop_checkUncheckedPopd3 = verifyNotTree checkUncheckedCdPushdPopd "set -e; popd; rm -r foo" -prop_checkUncheckedPopd4 = verifyNotTree checkUncheckedCdPushdPopd "if popd; then rm foo; fi" -prop_checkUncheckedPopd5 = verifyTree checkUncheckedCdPushdPopd "if true; then popd; fi" -prop_checkUncheckedPopd6 = verifyTree checkUncheckedCdPushdPopd "popd" -prop_checkUncheckedPopd7 = verifyNotTree checkUncheckedCdPushdPopd "#!/bin/bash -e\npopd\nrm bar" -prop_checkUncheckedPopd8 = verifyNotTree checkUncheckedCdPushdPopd "set -o errexit; popd; rm bar" -prop_checkUncheckedPopd9 = 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 @@ -2584,9 +2652,10 @@ checkUncheckedCdPushdPopd params root = [_, ".."] -> True; _ -> False -prop_checkLoopVariableReassignment1 = verify checkLoopVariableReassignment "for i in *; do for i in *.bar; do true; done; done" -prop_checkLoopVariableReassignment2 = verify checkLoopVariableReassignment "for i in *; do for((i=0; i<3; i++)); do true; done; done" -prop_checkLoopVariableReassignment3 = 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 @@ -2611,11 +2680,12 @@ checkLoopVariableReassignment params token = _ _ _ -> return var _ -> fail "not loop" -prop_checkTrailingBracket1 = verify checkTrailingBracket "if -z n ]]; then true; fi " -prop_checkTrailingBracket2 = verifyNot checkTrailingBracket "if [[ -z n ]]; then true; fi " -prop_checkTrailingBracket3 = verify checkTrailingBracket "a || b ] && thing" -prop_checkTrailingBracket4 = verifyNot checkTrailingBracket "run [ foo ]" -prop_checkTrailingBracket5 = 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 @@ -2637,15 +2707,16 @@ checkTrailingBracket _ token = "]" -> "[" x -> x -prop_checkReturnAgainstZero1 = verify checkReturnAgainstZero "[ $? -eq 0 ]" -prop_checkReturnAgainstZero2 = verify checkReturnAgainstZero "[[ \"$?\" -gt 0 ]]" -prop_checkReturnAgainstZero3 = verify checkReturnAgainstZero "[[ 0 -ne $? ]]" -prop_checkReturnAgainstZero4 = verifyNot checkReturnAgainstZero "[[ $? -eq 4 ]]" -prop_checkReturnAgainstZero5 = verify checkReturnAgainstZero "[[ 0 -eq $? ]]" -prop_checkReturnAgainstZero6 = verifyNot checkReturnAgainstZero "[[ $R -eq 0 ]]" -prop_checkReturnAgainstZero7 = verify checkReturnAgainstZero "(( $? == 0 ))" -prop_checkReturnAgainstZero8 = verify checkReturnAgainstZero "(( $? ))" -prop_checkReturnAgainstZero9 = 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 @@ -2667,14 +2738,15 @@ checkReturnAgainstZero _ token = _ -> False message id = style id 2181 "Check exit code directly with e.g. 'if mycmd;', not indirectly with $?." -prop_checkRedirectedNowhere1 = verify checkRedirectedNowhere "> file" -prop_checkRedirectedNowhere2 = verify checkRedirectedNowhere "> file | grep foo" -prop_checkRedirectedNowhere3 = verify checkRedirectedNowhere "grep foo | > bar" -prop_checkRedirectedNowhere4 = verifyNot checkRedirectedNowhere "grep foo > bar" -prop_checkRedirectedNowhere5 = verifyNot checkRedirectedNowhere "foo | grep bar > baz" -prop_checkRedirectedNowhere6 = verifyNot checkRedirectedNowhere "var=$(value) 2> /dev/null" -prop_checkRedirectedNowhere7 = verifyNot checkRedirectedNowhere "var=$(< file)" -prop_checkRedirectedNowhere8 = 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 @@ -2700,15 +2772,16 @@ checkRedirectedNowhere params token = _ -> Nothing -prop_checkArrayAssignmentIndices1 = verifyTree checkArrayAssignmentIndices "declare -A foo; foo=(bar)" -prop_checkArrayAssignmentIndices2 = verifyNotTree checkArrayAssignmentIndices "declare -a foo; foo=(bar)" -prop_checkArrayAssignmentIndices3 = verifyNotTree checkArrayAssignmentIndices "declare -A foo; foo=([i]=bar)" -prop_checkArrayAssignmentIndices4 = verifyTree checkArrayAssignmentIndices "typeset -A foo; foo+=(bar)" -prop_checkArrayAssignmentIndices5 = verifyTree checkArrayAssignmentIndices "arr=( [foo]= bar )" -prop_checkArrayAssignmentIndices6 = verifyTree checkArrayAssignmentIndices "arr=( [foo] = bar )" -prop_checkArrayAssignmentIndices7 = verifyTree checkArrayAssignmentIndices "arr=( var=value )" -prop_checkArrayAssignmentIndices8 = verifyNotTree checkArrayAssignmentIndices "arr=( [foo]=bar )" -prop_checkArrayAssignmentIndices9 = 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 @@ -2742,15 +2815,16 @@ checkArrayAssignmentIndices params root = _ -> return () -prop_checkUnmatchableCases1 = verify checkUnmatchableCases "case foo in bar) true; esac" -prop_checkUnmatchableCases2 = verify checkUnmatchableCases "case foo-$bar in ??|*) true; esac" -prop_checkUnmatchableCases3 = verify checkUnmatchableCases "case foo in foo) true; esac" -prop_checkUnmatchableCases4 = verifyNot checkUnmatchableCases "case foo-$bar in foo*|*bar|*baz*) true; esac" -prop_checkUnmatchableCases5 = verify checkUnmatchableCases "case $f in *.txt) true;; f??.txt) false;; esac" -prop_checkUnmatchableCases6 = verifyNot checkUnmatchableCases "case $f in ?*) true;; *) false;; esac" -prop_checkUnmatchableCases7 = verifyNot checkUnmatchableCases "case $f in $(x)) true;; asdf) false;; esac" -prop_checkUnmatchableCases8 = verify checkUnmatchableCases "case $f in cow) true;; bar|cow) false;; esac" -prop_checkUnmatchableCases9 = 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 @@ -2796,13 +2870,14 @@ checkUnmatchableCases _ t = checkDoms _ = return () -prop_checkSubshellAsTest1 = verify checkSubshellAsTest "( -e file )" -prop_checkSubshellAsTest2 = verify checkSubshellAsTest "( 1 -gt 2 )" -prop_checkSubshellAsTest3 = verifyNot checkSubshellAsTest "( grep -c foo bar )" -prop_checkSubshellAsTest4 = verifyNot checkSubshellAsTest "[ 1 -gt 2 ]" -prop_checkSubshellAsTest5 = verify checkSubshellAsTest "( -e file && -x file )" -prop_checkSubshellAsTest6 = verify checkSubshellAsTest "( -e file || -x file && -t 1 )" -prop_checkSubshellAsTest7 = 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 @@ -2824,14 +2899,15 @@ checkSubshellAsTest _ t = warn id 2205 "(..) is a subshell. Did you mean [ .. ], a test expression?" -prop_checkSplittingInArrays1 = verify checkSplittingInArrays "a=( $var )" -prop_checkSplittingInArrays2 = verify checkSplittingInArrays "a=( $(cmd) )" -prop_checkSplittingInArrays3 = verifyNot checkSplittingInArrays "a=( \"$var\" )" -prop_checkSplittingInArrays4 = verifyNot checkSplittingInArrays "a=( \"$(cmd)\" )" -prop_checkSplittingInArrays5 = verifyNot checkSplittingInArrays "a=( $! $$ $# )" -prop_checkSplittingInArrays6 = verifyNot checkSplittingInArrays "a=( ${#arr[@]} )" -prop_checkSplittingInArrays7 = verifyNot checkSplittingInArrays "a=( foo{1,2} )" -prop_checkSplittingInArrays8 = 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 @@ -2861,10 +2937,11 @@ checkSplittingInArrays params t = else "Prefer mapfile or read -a to split command output (or quote to avoid splitting)." -prop_checkRedirectionToNumber1 = verify checkRedirectionToNumber "( 1 > 2 )" -prop_checkRedirectionToNumber2 = verify checkRedirectionToNumber "foo 1>2" -prop_checkRedirectionToNumber3 = verifyNot checkRedirectionToNumber "echo foo > '2'" -prop_checkRedirectionToNumber4 = 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 @@ -2872,9 +2949,10 @@ checkRedirectionToNumber _ t = case t of return $ warn id 2210 "This is a file redirection. Was it supposed to be a comparison or fd operation?" _ -> return () -prop_checkGlobAsCommand1 = verify checkGlobAsCommand "foo*" -prop_checkGlobAsCommand2 = verify checkGlobAsCommand "$(var[i])" -prop_checkGlobAsCommand3 = 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) $ @@ -2882,10 +2960,11 @@ checkGlobAsCommand _ t = case t of _ -> return () -prop_checkFlagAsCommand1 = verify checkFlagAsCommand "-e file" -prop_checkFlagAsCommand2 = verify checkFlagAsCommand "foo\n --bar=baz" -prop_checkFlagAsCommand3 = verifyNot checkFlagAsCommand "'--myexec--' args" -prop_checkFlagAsCommand4 = 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) $ @@ -2893,21 +2972,23 @@ checkFlagAsCommand _ t = case t of _ -> return () -prop_checkEmptyCondition1 = verify checkEmptyCondition "if [ ]; then ..; fi" -prop_checkEmptyCondition2 = 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_checkPipeToNowhere1 = verify checkPipeToNowhere "foo | echo bar" -prop_checkPipeToNowhere2 = verify checkPipeToNowhere "basename < file.txt" -prop_checkPipeToNowhere3 = verify checkPipeToNowhere "printf 'Lol' <<< str" -prop_checkPipeToNowhere4 = verify checkPipeToNowhere "printf 'Lol' << eof\nlol\neof\n" -prop_checkPipeToNowhere5 = verifyNot checkPipeToNowhere "echo foo | xargs du" -prop_checkPipeToNowhere6 = verifyNot checkPipeToNowhere "ls | echo $(cat)" -prop_checkPipeToNowhere7 = verifyNot checkPipeToNowhere "echo foo | var=$(cat) ls" -prop_checkPipeToNowhere8 = verify checkPipeToNowhere "foo | true" -prop_checkPipeToNowhere9 = 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 @@ -2959,10 +3040,11 @@ checkPipeToNowhere _ t = T_FdRedirect _ _ T_HereString {} -> True _ -> False -prop_checkUseBeforeDefinition1 = verifyTree checkUseBeforeDefinition "f; f() { true; }" -prop_checkUseBeforeDefinition2 = verifyNotTree checkUseBeforeDefinition "f() { true; }; f" -prop_checkUseBeforeDefinition3 = verifyNotTree checkUseBeforeDefinition "if ! mycmd --version; then mycmd() { true; }; fi" -prop_checkUseBeforeDefinition4 = 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 @@ -2990,9 +3072,10 @@ checkUseBeforeDefinition _ t = then [x] else concatMap recursiveSequences list -prop_checkForLoopGlobVariables1 = verify checkForLoopGlobVariables "for i in $var/*.txt; do true; done" -prop_checkForLoopGlobVariables2 = verifyNot checkForLoopGlobVariables "for i in \"$var\"/*.txt; do true; done" -prop_checkForLoopGlobVariables3 = 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 @@ -3004,10 +3087,11 @@ checkForLoopGlobVariables _ t = suggest t = info (getId t) 2231 "Quote expansions in this for loop glob to prevent wordsplitting, e.g. \"$dir\"/*.txt ." -prop_checkSubshelledTests1 = verify checkSubshelledTests "a && ( [ b ] || ! [ c ] )" -prop_checkSubshelledTests2 = verify checkSubshelledTests "( [ a ] )" -prop_checkSubshelledTests3 = verify checkSubshelledTests "( [ a ] && [ b ] || test c )" -prop_checkSubshelledTests4 = 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 -> @@ -3068,11 +3152,12 @@ checkSubshelledTests params t = T_Annotation {} -> True _ -> False -prop_checkInvertedStringTest1 = verify checkInvertedStringTest "[ ! -z $var ]" -prop_checkInvertedStringTest2 = verify checkInvertedStringTest "! [[ -n $var ]]" -prop_checkInvertedStringTest3 = verifyNot checkInvertedStringTest "! [ -x $var ]" -prop_checkInvertedStringTest4 = verifyNot checkInvertedStringTest "[[ ! -w $var ]]" -prop_checkInvertedStringTest5 = 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 _) -> @@ -3088,9 +3173,9 @@ checkInvertedStringTest _ t = _ -> return () _ -> return () -prop_checkRedirectionToCommand1 = verify checkRedirectionToCommand "ls > rm" -prop_checkRedirectionToCommand2 = verifyNot checkRedirectionToCommand "ls > 'rm'" -prop_checkRedirectionToCommand3 = verifyNot checkRedirectionToCommand "ls > myfile" +-- >>> prop $ verify checkRedirectionToCommand "ls > rm" +-- >>> prop $ verifyNot checkRedirectionToCommand "ls > 'rm'" +-- >>> prop $ verifyNot checkRedirectionToCommand "ls > myfile" checkRedirectionToCommand _ t = case t of T_IoFile _ _ (T_NormalWord id [T_Literal _ str]) | str `elem` commonCommands -> @@ -3098,5 +3183,3 @@ checkRedirectionToCommand _ t = warn id 2238 "Redirecting to/from command name instead of file. Did you want pipes/xargs (or quote to ignore)?" _ -> return () -return [] -runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |]) diff --git a/src/ShellCheck/AnalyzerLib.hs b/src/ShellCheck/AnalyzerLib.hs index 306c139..df5b181 100644 --- a/src/ShellCheck/AnalyzerLib.hs +++ b/src/ShellCheck/AnalyzerLib.hs @@ -18,30 +18,29 @@ along with this program. If not, see . -} {-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE TemplateHaskell #-} module ShellCheck.AnalyzerLib where +import ShellCheck.AST +import ShellCheck.ASTLib +import ShellCheck.Data +import ShellCheck.Interface +import ShellCheck.Parser +import ShellCheck.Regex -import ShellCheck.AST -import ShellCheck.ASTLib -import ShellCheck.Data -import ShellCheck.Interface -import ShellCheck.Parser -import ShellCheck.Regex +import Control.Arrow (first) +import Control.DeepSeq +import Control.Monad.Identity +import Control.Monad.RWS +import Control.Monad.State +import Control.Monad.Writer +import Data.Char +import Data.List +import qualified Data.Map as Map +import Data.Maybe +import Data.Semigroup -import Control.Arrow (first) -import Control.DeepSeq -import Control.Monad.Identity -import Control.Monad.RWS -import Control.Monad.State -import Control.Monad.Writer -import Data.Char -import Data.List -import Data.Maybe -import Data.Semigroup -import qualified Data.Map as Map - -import Test.QuickCheck.All (forAllProperties) -import Test.QuickCheck.Test (maxSuccess, quickCheckWithResult, stdArgs) +prop :: Bool -> IO () +prop False = putStrLn "FAIL" +prop True = return () type Analysis = AnalyzerM () type AnalyzerM a = RWS Parameters [TokenComment] Cache a @@ -216,15 +215,15 @@ containsLastpipe root = _ -> False -prop_determineShell0 = determineShellTest "#!/bin/sh" == Sh -prop_determineShell1 = determineShellTest "#!/usr/bin/env ksh" == Ksh -prop_determineShell2 = determineShellTest "" == Bash -prop_determineShell3 = determineShellTest "#!/bin/sh -e" == Sh -prop_determineShell4 = determineShellTest "#!/bin/ksh\n#shellcheck shell=sh\nfoo" == Sh -prop_determineShell5 = determineShellTest "#shellcheck shell=sh\nfoo" == Sh -prop_determineShell6 = determineShellTest "#! /bin/sh" == Sh -prop_determineShell7 = determineShellTest "#! /bin/ash" == Dash - +-- | +-- >>> prop $ determineShellTest "#!/bin/sh" == Sh +-- >>> prop $ determineShellTest "#!/usr/bin/env ksh" == Ksh +-- >>> prop $ determineShellTest "" == Bash +-- >>> prop $ determineShellTest "#!/bin/sh -e" == Sh +-- >>> prop $ determineShellTest "#!/bin/ksh\n#shellcheck shell=sh\nfoo" == Sh +-- >>> prop $ determineShellTest "#shellcheck shell=sh\nfoo" == Sh +-- >>> prop $ determineShellTest "#! /bin/sh" == Sh +-- >>> prop $ determineShellTest "#! /bin/ash" == Dash determineShellTest = determineShell . fromJust . prRoot . pScript determineShell t = fromMaybe Bash $ do shellString <- foldl mplus Nothing $ getCandidates t @@ -667,10 +666,11 @@ getIndexReferences s = fromMaybe [] $ do where re = mkRegex "(\\[.*\\])" -prop_getOffsetReferences1 = getOffsetReferences ":bar" == ["bar"] -prop_getOffsetReferences2 = getOffsetReferences ":bar:baz" == ["bar", "baz"] -prop_getOffsetReferences3 = getOffsetReferences "[foo]:bar" == ["bar"] -prop_getOffsetReferences4 = 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 @@ -745,9 +745,15 @@ isUnqualifiedCommand token str = isCommandMatch token (== str) isCommandMatch token matcher = fromMaybe False $ fmap matcher (getCommandName token) +-- | -- Does this regex look like it was intended as a glob? --- True: *foo* --- False: .*foo.* +-- +-- >>> isConfusedGlobRegex "*foo*" +-- True +-- +-- >>> isConfusedGlobRegex ".*foo.*" +-- False +-- isConfusedGlobRegex :: String -> Bool isConfusedGlobRegex ('*':_) = True isConfusedGlobRegex [x,'*'] | x /= '\\' = True @@ -757,9 +763,10 @@ isVariableStartChar x = x == '_' || isAsciiLower x || isAsciiUpper x isVariableChar x = isVariableStartChar x || isDigit x variableNameRegex = mkRegex "[_a-zA-Z][_a-zA-Z0-9]*" -prop_isVariableName1 = isVariableName "_fo123" -prop_isVariableName2 = not $ isVariableName "4" -prop_isVariableName3 = not $ isVariableName "test: " +-- | +-- >>> prop $ isVariableName "_fo123" +-- >>> prop $ not $ isVariableName "4" +-- >>> prop $ not $ isVariableName "test: " isVariableName (x:r) = isVariableStartChar x && all isVariableChar r isVariableName _ = False @@ -768,27 +775,28 @@ getVariablesFromLiteralToken token = -- Try to get referenced variables from a literal string like "$foo" -- Ignores tons of cases like arithmetic evaluation and array indices. -prop_getVariablesFromLiteral1 = - 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 variableRegex = mkRegex "\\$\\{?([A-Za-z0-9_]+)" +-- | -- Get the variable name from an expansion like ${var:-foo} -prop_getBracedReference1 = getBracedReference "foo" == "foo" -prop_getBracedReference2 = getBracedReference "#foo" == "foo" -prop_getBracedReference3 = getBracedReference "#" == "#" -prop_getBracedReference4 = getBracedReference "##" == "#" -prop_getBracedReference5 = getBracedReference "#!" == "!" -prop_getBracedReference6 = getBracedReference "!#" == "#" -prop_getBracedReference7 = getBracedReference "!foo#?" == "foo" -prop_getBracedReference8 = getBracedReference "foo-bar" == "foo" -prop_getBracedReference9 = getBracedReference "foo:-bar" == "foo" -prop_getBracedReference10= getBracedReference "foo: -1" == "foo" -prop_getBracedReference11= getBracedReference "!os*" == "" -prop_getBracedReference12= getBracedReference "!os?bar**" == "" -prop_getBracedReference13= 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 @@ -811,9 +819,10 @@ getBracedReference s = fromMaybe s $ return "" nameExpansion _ = Nothing -prop_getBracedModifier1 = getBracedModifier "foo:bar:baz" == ":bar:baz" -prop_getBracedModifier2 = getBracedModifier "!var:-foo" == ":-foo" -prop_getBracedModifier3 = 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 @@ -830,10 +839,13 @@ getBracedModifier s = fromMaybe "" . listToMaybe $ do -- Run an action in a Maybe (or do nothing). -- Example: +-- +-- @ -- potentially $ do -- s <- getLiteralString cmd -- guard $ s `elem` ["--recursive", "-r"] -- return $ warn .. "Something something recursive" +-- @ potentially :: Monad m => Maybe (m ()) -> m () potentially = fromMaybe (return ()) @@ -918,6 +930,3 @@ getOpts flagTokenizer string cmd = process flags else do more <- process rest2 return $ (flag1, token1) : more - -return [] -runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |]) diff --git a/src/ShellCheck/Checker.hs b/src/ShellCheck/Checker.hs index 10074e3..9a040ee 100644 --- a/src/ShellCheck/Checker.hs +++ b/src/ShellCheck/Checker.hs @@ -17,8 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -{-# LANGUAGE TemplateHaskell #-} -module ShellCheck.Checker (checkScript, ShellCheck.Checker.runTests) where +module ShellCheck.Checker (checkScript) where import ShellCheck.Interface import ShellCheck.Parser @@ -35,8 +34,6 @@ import qualified System.IO import Prelude hiding (readFile) import Control.Monad -import Test.QuickCheck.All - tokenToPosition startMap t = fromMaybe fail $ do span <- Map.lookup (tcId t) startMap return $ newPositionedComment { @@ -125,113 +122,132 @@ checkRecursive includes src = csCheckSourced = True } -prop_findsParseIssue = check "echo \"$12\"" == [1037] - -prop_commentDisablesParseIssue1 = - null $ check "#shellcheck disable=SC1037\necho \"$12\"" -prop_commentDisablesParseIssue2 = - null $ check "#shellcheck disable=SC1037\n#lol\necho \"$12\"" - -prop_findsAnalysisIssue = - check "echo $1" == [2086] -prop_commentDisablesAnalysisIssue1 = - null $ check "#shellcheck disable=SC2086\necho $1" -prop_commentDisablesAnalysisIssue2 = - null $ check "#shellcheck disable=SC2086\n#lol\necho $1" - -prop_optionDisablesIssue1 = - null $ getErrors - (mockedSystemInterface []) - emptyCheckSpec { - csScript = "echo $1", - csExcludedWarnings = [2148, 2086] - } - -prop_optionDisablesIssue2 = - null $ getErrors - (mockedSystemInterface []) - emptyCheckSpec { - csScript = "echo \"$10\"", - csExcludedWarnings = [2148, 1037] - } - -prop_wontParseBadShell = - [1071] == check "#!/usr/bin/python\ntrue $1\n" - -prop_optionDisablesBadShebang = - null $ getErrors - (mockedSystemInterface []) - emptyCheckSpec { - csScript = "#!/usr/bin/python\ntrue\n", - csShellTypeOverride = Just Sh - } - -prop_annotationDisablesBadShebang = - [] == check "#!/usr/bin/python\n# shellcheck shell=sh\ntrue\n" - - -prop_canParseDevNull = - [] == check "source /dev/null" - -prop_failsWhenNotSourcing = - [1091, 2154] == check "source lol; echo \"$bar\"" - -prop_worksWhenSourcing = - null $ checkWithIncludes [("lib", "bar=1")] "source lib; echo \"$bar\"" - -prop_worksWhenDotting = - null $ checkWithIncludes [("lib", "bar=1")] ". lib; echo \"$bar\"" - -prop_noInfiniteSourcing = - [] == checkWithIncludes [("lib", "source lib")] "source lib" - -prop_canSourceBadSyntax = - [1094, 2086] == checkWithIncludes [("lib", "for f; do")] "source lib; echo $1" - -prop_cantSourceDynamic = - [1090] == checkWithIncludes [("lib", "")] ". \"$1\"" - -prop_cantSourceDynamic2 = - [1090] == checkWithIncludes [("lib", "")] "source ~/foo" - -prop_canSourceDynamicWhenRedirected = - null $ checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\"" - -prop_recursiveAnalysis = - [2086] == checkRecursive [("lib", "echo $1")] "source lib" - -prop_recursiveParsing = - [1037] == checkRecursive [("lib", "echo \"$10\"")] "source lib" - -prop_sourceDirectiveDoesntFollowFile = - null $ checkWithIncludes - [("foo", "source bar"), ("bar", "baz=3")] - "#shellcheck source=foo\n. \"$1\"; echo \"$baz\"" - -prop_filewideAnnotationBase = [2086] == check "#!/bin/sh\necho $1" -prop_filewideAnnotation1 = null $ - check "#!/bin/sh\n# shellcheck disable=2086\necho $1" -prop_filewideAnnotation2 = null $ - check "#!/bin/sh\n# shellcheck disable=2086\ntrue\necho $1" -prop_filewideAnnotation3 = null $ - check "#!/bin/sh\n#unrelated\n# shellcheck disable=2086\ntrue\necho $1" -prop_filewideAnnotation4 = null $ - check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1" -prop_filewideAnnotation5 = null $ - check "#!/bin/sh\n\n\n\n#shellcheck disable=2086\ntrue\necho $1" -prop_filewideAnnotation6 = null $ - check "#shellcheck shell=sh\n#unrelated\n#shellcheck disable=2086\ntrue\necho $1" -prop_filewideAnnotation7 = null $ - check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1" - -prop_filewideAnnotationBase2 = [2086, 2181] == check "true\n[ $? == 0 ] && echo $1" -prop_filewideAnnotation8 = null $ - check "# Disable $? warning\n#shellcheck disable=SC2181\n# Disable quoting warning\n#shellcheck disable=2086\ntrue\n[ $? == 0 ] && echo $1" - -prop_sourcePartOfOriginalScript = -- #1181: -x disabled posix warning for 'source' - 2039 `elem` checkWithIncludes [("./saywhat.sh", "echo foo")] "#!/bin/sh\nsource ./saywhat.sh" - -prop_spinBug1413 = null $ check "fun() {\n# shellcheck disable=SC2188\n> /dev/null\n}\n" - -return [] -runTests = $quickCheckAll +-- | Dummy binding for doctest to run +-- +-- >>> check "echo \"$12\"" +-- [1037] +-- +-- >>> check "#shellcheck disable=SC1037\necho \"$12\"" +-- [] +-- +-- >>> check "#shellcheck disable=SC1037\n#lol\necho \"$12\"" +-- [] +-- +-- >>> check "echo $1" +-- [2086] +-- +-- >>> check "#shellcheck disable=SC2086\necho $1" +-- [] +-- +-- >>> check "#shellcheck disable=SC2086\n#lol\necho $1" +-- [] +-- +-- >>> :{ +-- getErrors +-- (mockedSystemInterface []) +-- emptyCheckSpec { +-- csScript = "echo $1", +-- csExcludedWarnings = [2148, 2086] +-- } +-- :} +-- [] +-- +-- >>> :{ +-- getErrors +-- (mockedSystemInterface []) +-- emptyCheckSpec { +-- csScript = "echo \"$10\"", +-- csExcludedWarnings = [2148, 1037] +-- } +-- :} +-- [] +-- +-- >>> check "#!/usr/bin/python\ntrue $1\n" +-- [1071] +-- +-- >>> :{ +-- getErrors +-- (mockedSystemInterface []) +-- emptyCheckSpec { +-- csScript = "#!/usr/bin/python\ntrue\n", +-- csShellTypeOverride = Just Sh +-- } +-- :} +-- [] +-- +-- >>> check "#!/usr/bin/python\n# shellcheck shell=sh\ntrue\n" +-- [] +-- +-- >>> check "source /dev/null" +-- [] +-- +-- >>> check "source lol; echo \"$bar\"" +-- [1091,2154] +-- +-- >>> checkWithIncludes [("lib", "bar=1")] "source lib; echo \"$bar\"" +-- [] +-- +-- >>> checkWithIncludes [("lib", "bar=1")] ". lib; echo \"$bar\"" +-- [] +-- +-- >>> checkWithIncludes [("lib", "source lib")] "source lib" +-- [] +-- +-- >>> checkWithIncludes [("lib", "for f; do")] "source lib; echo $1" +-- [1094,2086] +-- +-- >>> checkWithIncludes [("lib", "")] ". \"$1\"" +-- [1090] +-- +-- >>> checkWithIncludes [("lib", "")] "source ~/foo" +-- [1090] +-- +-- >>> checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\"" +-- [] +-- +-- >>> checkRecursive [("lib", "echo $1")] "source lib" +-- [2086] +-- +-- >>> checkRecursive [("lib", "echo \"$10\"")] "source lib" +-- [1037] +-- +-- >>> checkWithIncludes [("foo", "source bar"), ("bar", "baz=3")] "#shellcheck source=foo\n. \"$1\"; echo \"$baz\"" +-- [] +-- +-- >>> check "#!/bin/sh\necho $1" +-- [2086] +-- +-- >>> check "#!/bin/sh\n# shellcheck disable=2086\necho $1" +-- [] +-- +-- >>> check "#!/bin/sh\n# shellcheck disable=2086\ntrue\necho $1" +-- [] +-- +-- >>> check "#!/bin/sh\n#unrelated\n# shellcheck disable=2086\ntrue\necho $1" +-- [] +-- +-- >>> check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1" +-- [] +-- +-- >>> check "#!/bin/sh\n\n\n\n#shellcheck disable=2086\ntrue\necho $1" +-- [] +-- +-- >>> check "#shellcheck shell=sh\n#unrelated\n#shellcheck disable=2086\ntrue\necho $1" +-- [] +-- +-- >>> check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1" +-- [] +-- +-- check "true\n[ $? == 0 ] && echo $1" +-- [2086, 2181] +-- +-- check "# Disable $? warning\n#shellcheck disable=SC2181\n# Disable quoting warning\n#shellcheck disable=2086\ntrue\n[ $? == 0 ] && echo $1" +-- [] +-- +-- >>> 2039 `elem` checkWithIncludes [("./saywhat.sh", "echo foo")] "#!/bin/sh\nsource ./saywhat.sh" +-- True +-- +-- >>> check "fun() {\n# shellcheck disable=SC2188\n> /dev/null\n}\n" +-- [] +doctests :: () +doctests = () diff --git a/src/ShellCheck/Checks/Commands.hs b/src/ShellCheck/Checks/Commands.hs index f4ead5b..8faae36 100644 --- a/src/ShellCheck/Checks/Commands.hs +++ b/src/ShellCheck/Checks/Commands.hs @@ -17,11 +17,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} - -- This module contains checks that examine specific commands by name. -module ShellCheck.Checks.Commands (checker , ShellCheck.Checks.Commands.runTests) where +module ShellCheck.Checks.Commands (checker) where import ShellCheck.AST import ShellCheck.ASTLib @@ -37,8 +35,6 @@ import Data.Char import Data.List import Data.Maybe import qualified Data.Map.Strict as Map -import Test.QuickCheck.All (forAllProperties) -import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess) data CommandName = Exactly String | Basename String deriving (Eq, Ord) @@ -46,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 @@ -130,20 +125,21 @@ getChecker list = Checker { checker :: Parameters -> Checker checker params = getChecker commandChecks -prop_checkTr1 = verify checkTr "tr [a-f] [A-F]" -prop_checkTr2 = verify checkTr "tr 'a-z' 'A-Z'" -prop_checkTr2a= verify checkTr "tr '[a-z]' '[A-Z]'" -prop_checkTr3 = verifyNot checkTr "tr -d '[:lower:]'" -prop_checkTr3a= verifyNot checkTr "tr -d '[:upper:]'" -prop_checkTr3b= verifyNot checkTr "tr -d '|/_[:upper:]'" -prop_checkTr4 = verifyNot checkTr "ls [a-z]" -prop_checkTr5 = verify checkTr "tr foo bar" -prop_checkTr6 = verify checkTr "tr 'hello' 'world'" -prop_checkTr8 = verifyNot checkTr "tr aeiou _____" -prop_checkTr9 = verifyNot checkTr "a-z n-za-m" -prop_checkTr10= verifyNot checkTr "tr --squeeze-repeats rl lr" -prop_checkTr11= verifyNot checkTr "tr abc '[d*]'" -prop_checkTr12= 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? @@ -164,9 +160,10 @@ checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments) let relevant = filter isAlpha s in relevant /= nub relevant -prop_checkFindNameGlob1 = verify checkFindNameGlob "find / -name *.php" -prop_checkFindNameGlob2 = verify checkFindNameGlob "find / -type f -ipath *(foo)" -prop_checkFindNameGlob3 = 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 @@ -179,10 +176,11 @@ checkFindNameGlob = CommandCheck (Basename "find") (f . arguments) where f (b:r) -prop_checkNeedlessExpr = verify checkNeedlessExpr "foo=$(expr 3 + 2)" -prop_checkNeedlessExpr2 = verify checkNeedlessExpr "foo=`echo \\`expr 3 + 2\\``" -prop_checkNeedlessExpr3 = verifyNot checkNeedlessExpr "foo=$(expr foo : regex)" -prop_checkNeedlessExpr4 = 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)) $ @@ -193,21 +191,22 @@ checkNeedlessExpr = CommandCheck (Basename "expr") f where words = mapMaybe getLiteralString -prop_checkGrepRe1 = verify checkGrepRe "cat foo | grep *.mp3" -prop_checkGrepRe2 = verify checkGrepRe "grep -Ev cow*test *.mp3" -prop_checkGrepRe3 = verify checkGrepRe "grep --regex=*.mp3 file" -prop_checkGrepRe4 = verifyNot checkGrepRe "grep foo *.mp3" -prop_checkGrepRe5 = verifyNot checkGrepRe "grep-v --regex=moo *" -prop_checkGrepRe6 = verifyNot checkGrepRe "grep foo \\*.mp3" -prop_checkGrepRe7 = verify checkGrepRe "grep *foo* file" -prop_checkGrepRe8 = verify checkGrepRe "ls | grep foo*.jpg" -prop_checkGrepRe9 = verifyNot checkGrepRe "grep '[0-9]*' file" -prop_checkGrepRe10= verifyNot checkGrepRe "grep '^aa*' file" -prop_checkGrepRe11= verifyNot checkGrepRe "grep --include=*.png foo" -prop_checkGrepRe12= verifyNot checkGrepRe "grep -F 'Foo*' file" -prop_checkGrepRe13= verifyNot checkGrepRe "grep -- -foo bar*" -prop_checkGrepRe14= verifyNot checkGrepRe "grep -e -foo bar*" -prop_checkGrepRe15= 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) @@ -258,10 +257,11 @@ checkGrepRe = CommandCheck (Basename "grep") check where contra = mkRegex "[^a-zA-Z1-9]\\*|[][^$+\\\\]" -prop_checkTrapQuotes1 = verify checkTrapQuotes "trap \"echo $num\" INT" -prop_checkTrapQuotes1a= verify checkTrapQuotes "trap \"echo `ls`\" INT" -prop_checkTrapQuotes2 = verifyNot checkTrapQuotes "trap 'echo $num' INT" -prop_checkTrapQuotes3 = 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,24 +275,26 @@ checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where checkExpansions _ = return () -prop_checkReturn1 = verifyNot checkReturn "return" -prop_checkReturn2 = verifyNot checkReturn "return 1" -prop_checkReturn3 = verifyNot checkReturn "return $var" -prop_checkReturn4 = verifyNot checkReturn "return $((a|b))" -prop_checkReturn5 = verify checkReturn "return -1" -prop_checkReturn6 = verify checkReturn "return 1000" -prop_checkReturn7 = 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") (returnOrExit (\c -> err c 2151 "Only one integer 0-255 can be returned. Use stdout for other data.") (\c -> err c 2152 "Can only return 0-255. Other data should be written to stdout.")) -prop_checkExit1 = verifyNot checkExit "exit" -prop_checkExit2 = verifyNot checkExit "exit 1" -prop_checkExit3 = verifyNot checkExit "exit $var" -prop_checkExit4 = verifyNot checkExit "exit $((a|b))" -prop_checkExit5 = verify checkExit "exit -1" -prop_checkExit6 = verify checkExit "exit 1000" -prop_checkExit7 = verify checkExit "exit 'hello world'" +-- | +-- >>> prop $ verifyNot checkExit "exit" +-- >>> prop $ verifyNot checkExit "exit 1" +-- >>> prop $ verifyNot checkExit "exit $var" +-- >>> prop $ verifyNot checkExit "exit $((a|b))" +-- >>> prop $ verify checkExit "exit -1" +-- >>> prop $ verify checkExit "exit 1000" +-- >>> prop $ verify checkExit "exit 'hello world'" checkExit = CommandCheck (Exactly "exit") (returnOrExit (\c -> err c 2241 "The exit status can only be one integer 0-255. Use stdout for other data.") (\c -> err c 2242 "Can only exit with status 0-255. Other data should be written to stdout/stderr.")) @@ -317,9 +319,10 @@ returnOrExit multi invalid = (f . arguments) lit _ = return "WTF" -prop_checkFindExecWithSingleArgument1 = verify checkFindExecWithSingleArgument "find . -exec 'cat {} | wc -l' \\;" -prop_checkFindExecWithSingleArgument2 = verify checkFindExecWithSingleArgument "find . -execdir 'cat {} | wc -l' +" -prop_checkFindExecWithSingleArgument3 = 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 @@ -335,11 +338,12 @@ checkFindExecWithSingleArgument = CommandCheck (Basename "find") (f . arguments) commandRegex = mkRegex "[ |;]" -prop_checkUnusedEchoEscapes1 = verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n'" -prop_checkUnusedEchoEscapes2 = verifyNot checkUnusedEchoEscapes "echo -e 'foi\\nbar'" -prop_checkUnusedEchoEscapes3 = verify checkUnusedEchoEscapes "echo \"n:\\t42\"" -prop_checkUnusedEchoEscapes4 = verifyNot checkUnusedEchoEscapes "echo lol" -prop_checkUnusedEchoEscapes5 = 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]" @@ -354,9 +358,10 @@ checkUnusedEchoEscapes = CommandCheck (Basename "echo") f info (getId token) 2028 "echo may not expand escape sequences. Use printf." -prop_checkInjectableFindSh1 = verify checkInjectableFindSh "find . -exec sh -c 'echo {}' \\;" -prop_checkInjectableFindSh2 = verify checkInjectableFindSh "find . -execdir bash -c 'rm \"{}\"' ';'" -prop_checkInjectableFindSh3 = 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 @@ -379,9 +384,10 @@ checkInjectableFindSh = CommandCheck (Basename "find") (check . arguments) warn id 2156 "Injecting filenames is fragile and insecure. Use parameters." -prop_checkFindActionPrecedence1 = verify checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au' -exec rm {} +" -prop_checkFindActionPrecedence2 = verifyNot checkFindActionPrecedence "find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)" -prop_checkFindActionPrecedence3 = 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] @@ -398,28 +404,29 @@ checkFindActionPrecedence = CommandCheck (Basename "find") (f . arguments) warnFor t = warn (getId t) 2146 "This action ignores everything before the -o. Use \\( \\) to group." -prop_checkMkdirDashPM0 = verify checkMkdirDashPM "mkdir -p -m 0755 a/b" -prop_checkMkdirDashPM1 = verify checkMkdirDashPM "mkdir -pm 0755 $dir" -prop_checkMkdirDashPM2 = verify checkMkdirDashPM "mkdir -vpm 0755 a/b" -prop_checkMkdirDashPM3 = verify checkMkdirDashPM "mkdir -pm 0755 -v a/b" -prop_checkMkdirDashPM4 = verify checkMkdirDashPM "mkdir --parents --mode=0755 a/b" -prop_checkMkdirDashPM5 = verify checkMkdirDashPM "mkdir --parents --mode 0755 a/b" -prop_checkMkdirDashPM6 = verify checkMkdirDashPM "mkdir -p --mode=0755 a/b" -prop_checkMkdirDashPM7 = verify checkMkdirDashPM "mkdir --parents -m 0755 a/b" -prop_checkMkdirDashPM8 = verifyNot checkMkdirDashPM "mkdir -p a/b" -prop_checkMkdirDashPM9 = verifyNot checkMkdirDashPM "mkdir -m 0755 a/b" -prop_checkMkdirDashPM10 = verifyNot checkMkdirDashPM "mkdir a/b" -prop_checkMkdirDashPM11 = verifyNot checkMkdirDashPM "mkdir --parents a/b" -prop_checkMkdirDashPM12 = verifyNot checkMkdirDashPM "mkdir --mode=0755 a/b" -prop_checkMkdirDashPM13 = verifyNot checkMkdirDashPM "mkdir_func -pm 0755 a/b" -prop_checkMkdirDashPM14 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 singlelevel" -prop_checkMkdirDashPM15 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../bin" -prop_checkMkdirDashPM16 = verify checkMkdirDashPM "mkdir -p -m 0755 ../bin/laden" -prop_checkMkdirDashPM17 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./bin" -prop_checkMkdirDashPM18 = verify checkMkdirDashPM "mkdir -p -m 0755 ./bin/laden" -prop_checkMkdirDashPM19 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./../bin" -prop_checkMkdirDashPM20 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 .././bin" -prop_checkMkdirDashPM21 = 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 @@ -435,13 +442,14 @@ checkMkdirDashPM = CommandCheck (Basename "mkdir") check re = mkRegex "^(\\.\\.?\\/)+[^/]+$" -prop_checkNonportableSignals1 = verify checkNonportableSignals "trap f 8" -prop_checkNonportableSignals2 = verifyNot checkNonportableSignals "trap f 0" -prop_checkNonportableSignals3 = verifyNot checkNonportableSignals "trap f 14" -prop_checkNonportableSignals4 = verify checkNonportableSignals "trap f SIGKILL" -prop_checkNonportableSignals5 = verify checkNonportableSignals "trap f 9" -prop_checkNonportableSignals6 = verify checkNonportableSignals "trap f stop" -prop_checkNonportableSignals7 = 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 @@ -470,10 +478,11 @@ checkNonportableSignals = CommandCheck (Exactly "trap") (f . arguments) "SIGKILL/SIGSTOP can not be trapped." -prop_checkInteractiveSu1 = verify checkInteractiveSu "su; rm file; su $USER" -prop_checkInteractiveSu2 = verify checkInteractiveSu "su foo; something; exit" -prop_checkInteractiveSu3 = verifyNot checkInteractiveSu "echo rm | su foo" -prop_checkInteractiveSu4 = 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 @@ -488,11 +497,13 @@ checkInteractiveSu = CommandCheck (Basename "su") f undirected _ = True +-- | -- This is hard to get right without properly parsing ssh args -prop_checkSshCmdStr1 = verify checkSshCommandString "ssh host \"echo $PS1\"" -prop_checkSshCmdStr2 = verifyNot checkSshCommandString "ssh host \"ls foo\"" -prop_checkSshCmdStr3 = verifyNot checkSshCommandString "ssh \"$host\"" -prop_checkSshCmdStr4 = 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) @@ -508,24 +519,25 @@ checkSshCommandString = CommandCheck (Basename "ssh") (f . arguments) checkArg _ = return () -prop_checkPrintfVar1 = verify checkPrintfVar "printf \"Lol: $s\"" -prop_checkPrintfVar2 = verifyNot checkPrintfVar "printf 'Lol: $s'" -prop_checkPrintfVar3 = verify checkPrintfVar "printf -v cow $(cmd)" -prop_checkPrintfVar4 = verifyNot checkPrintfVar "printf \"%${count}s\" var" -prop_checkPrintfVar5 = verify checkPrintfVar "printf '%s %s %s' foo bar" -prop_checkPrintfVar6 = verify checkPrintfVar "printf foo bar baz" -prop_checkPrintfVar7 = verify checkPrintfVar "printf -- foo bar baz" -prop_checkPrintfVar8 = verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\"" -prop_checkPrintfVar9 = verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png" -prop_checkPrintfVar10= verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz" -prop_checkPrintfVar11= verifyNot checkPrintfVar "printf '%(%s%s)T' -1" -prop_checkPrintfVar12= verify checkPrintfVar "printf '%s %s\\n' 1 2 3" -prop_checkPrintfVar13= verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4" -prop_checkPrintfVar14= verify checkPrintfVar "printf '%*s\\n' 1" -prop_checkPrintfVar15= verifyNot checkPrintfVar "printf '%*s\\n' 1 2" -prop_checkPrintfVar16= verifyNot checkPrintfVar "printf $'string'" -prop_checkPrintfVar17= verify checkPrintfVar "printf '%-*s\\n' 1" -prop_checkPrintfVar18= 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 @@ -574,24 +586,26 @@ checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where -prop_checkUuoeCmd1 = verify checkUuoeCmd "echo $(date)" -prop_checkUuoeCmd2 = verify checkUuoeCmd "echo `date`" -prop_checkUuoeCmd3 = verify checkUuoeCmd "echo \"$(date)\"" -prop_checkUuoeCmd4 = verify checkUuoeCmd "echo \"`date`\"" -prop_checkUuoeCmd5 = verifyNot checkUuoeCmd "echo \"The time is $(date)\"" -prop_checkUuoeCmd6 = 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 \"$(>> 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 +625,11 @@ checkSetAssignment = CommandCheck (Exactly "set") (f . arguments) literal _ = "*" -prop_checkExportedExpansions1 = verify checkExportedExpansions "export $foo" -prop_checkExportedExpansions2 = verify checkExportedExpansions "export \"$foo\"" -prop_checkExportedExpansions3 = verifyNot checkExportedExpansions "export foo" -prop_checkExportedExpansions4 = 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 @@ -623,14 +638,15 @@ checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . argumen return . warn (getId t) 2163 $ "This does not export '" ++ name ++ "'. Remove $/${} for that, or use ${var?} to quiet." -prop_checkReadExpansions1 = verify checkReadExpansions "read $var" -prop_checkReadExpansions2 = verify checkReadExpansions "read -r $var" -prop_checkReadExpansions3 = verifyNot checkReadExpansions "read -p $var" -prop_checkReadExpansions4 = verifyNot checkReadExpansions "read -rd $delim name" -prop_checkReadExpansions5 = verify checkReadExpansions "read \"$var\"" -prop_checkReadExpansions6 = verify checkReadExpansions "read -a $var" -prop_checkReadExpansions7 = verifyNot checkReadExpansions "read $1" -prop_checkReadExpansions8 = 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:" @@ -657,9 +673,10 @@ getSingleUnmodifiedVariable word = in guard (contents == name) >> return t _ -> Nothing -prop_checkAliasesUsesArgs1 = verify checkAliasesUsesArgs "alias a='cp $1 /a'" -prop_checkAliasesUsesArgs2 = verifyNot checkAliasesUsesArgs "alias $1='foo'" -prop_checkAliasesUsesArgs3 = 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*@]" @@ -671,9 +688,10 @@ checkAliasesUsesArgs = CommandCheck (Exactly "alias") (f . arguments) "Aliases can't use positional parameters. Use a function." -prop_checkAliasesExpandEarly1 = verify checkAliasesExpandEarly "alias foo=\"echo $PWD\"" -prop_checkAliasesExpandEarly2 = verifyNot checkAliasesExpandEarly "alias -p" -prop_checkAliasesExpandEarly3 = 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 @@ -683,8 +701,8 @@ checkAliasesExpandEarly = CommandCheck (Exactly "alias") (f . arguments) checkArg _ = return () -prop_checkUnsetGlobs1 = verify checkUnsetGlobs "unset foo[1]" -prop_checkUnsetGlobs2 = 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 = @@ -692,14 +710,15 @@ checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments) warn (getId arg) 2184 "Quote arguments to unset so they're not glob expanded." -prop_checkFindWithoutPath1 = verify checkFindWithoutPath "find -type f" -prop_checkFindWithoutPath2 = verify checkFindWithoutPath "find" -prop_checkFindWithoutPath3 = verifyNot checkFindWithoutPath "find . -type f" -prop_checkFindWithoutPath4 = verifyNot checkFindWithoutPath "find -H -L \"$path\" -print" -prop_checkFindWithoutPath5 = verifyNot checkFindWithoutPath "find -O3 ." -prop_checkFindWithoutPath6 = verifyNot checkFindWithoutPath "find -D exec ." -prop_checkFindWithoutPath7 = verifyNot checkFindWithoutPath "find --help" -prop_checkFindWithoutPath8 = 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)) = @@ -718,10 +737,11 @@ checkFindWithoutPath = CommandCheck (Basename "find") f leadingFlagChars="-EHLPXdfsxO0123456789" -prop_checkTimeParameters1 = verify checkTimeParameters "time -f lol sleep 10" -prop_checkTimeParameters2 = verifyNot checkTimeParameters "time sleep 10" -prop_checkTimeParameters3 = verifyNot checkTimeParameters "time -p foo" -prop_checkTimeParameters4 = 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:_)) = @@ -732,9 +752,10 @@ checkTimeParameters = CommandCheck (Exactly "time") f f _ = return () -prop_checkTimedCommand1 = verify checkTimedCommand "#!/bin/sh\ntime -p foo | bar" -prop_checkTimedCommand2 = verify checkTimedCommand "#!/bin/dash\ntime ( foo; bar; )" -prop_checkTimedCommand3 = 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 @@ -758,32 +779,37 @@ checkTimedCommand = CommandCheck (Exactly "time") f where T_SimpleCommand {} -> return True _ -> return False -prop_checkLocalScope1 = verify checkLocalScope "local foo=3" -prop_checkLocalScope2 = 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 unless (any isFunction path) $ err (getId $ getCommandTokenOrThis t) 2168 "'local' is only valid in functions." -prop_checkDeprecatedTempfile1 = verify checkDeprecatedTempfile "var=$(tempfile)" -prop_checkDeprecatedTempfile2 = 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_checkDeprecatedEgrep = 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_checkDeprecatedFgrep = 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_checkWhileGetoptsCase1 = verify checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; esac; done" -prop_checkWhileGetoptsCase2 = verify checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done" -prop_checkWhileGetoptsCase3 = verifyNot checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done" -prop_checkWhileGetoptsCase4 = verifyNot checkWhileGetoptsCase "while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done" -prop_checkWhileGetoptsCase5 = 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 @@ -848,19 +874,20 @@ checkWhileGetoptsCase = CommandCheck (Exactly "getopts") f T_Redirecting _ _ x@(T_CaseExpression {}) -> return x _ -> Nothing -prop_checkCatastrophicRm1 = verify checkCatastrophicRm "rm -r $1/$2" -prop_checkCatastrophicRm2 = verify checkCatastrophicRm "rm -r /home/$foo" -prop_checkCatastrophicRm3 = verifyNot checkCatastrophicRm "rm -r /home/${USER:?}/*" -prop_checkCatastrophicRm4 = verify checkCatastrophicRm "rm -fr /home/$(whoami)/*" -prop_checkCatastrophicRm5 = verifyNot checkCatastrophicRm "rm -r /home/${USER:-thing}/*" -prop_checkCatastrophicRm6 = verify checkCatastrophicRm "rm --recursive /etc/*$config*" -prop_checkCatastrophicRm8 = verify checkCatastrophicRm "rm -rf /home" -prop_checkCatastrophicRm10= verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}" -prop_checkCatastrophicRm11= verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec" -prop_checkCatastrophicRm12= verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbin}}/$exec" -prop_checkCatastrophicRm13= verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}/$exec" -prop_checkCatastrophicRmA = verify checkCatastrophicRm "rm -rf /usr /lib/nvidia-current/xorg/xorg" -prop_checkCatastrophicRmB = 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 @@ -909,8 +936,9 @@ checkCatastrophicRm = CommandCheck (Basename "rm") $ \t -> ["", "/", "/*", "/*/*"] >>= (\x -> map (++x) paths) -prop_checkLetUsage1 = verify checkLetUsage "let a=1" -prop_checkLetUsage2 = 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 @@ -930,15 +958,16 @@ missingDestination handler token = do any (\x -> x /= "" && x `isPrefixOf` "target-directory") $ map snd args -prop_checkMvArguments1 = verify checkMvArguments "mv 'foo bar'" -prop_checkMvArguments2 = verifyNot checkMvArguments "mv foo bar" -prop_checkMvArguments3 = verifyNot checkMvArguments "mv 'foo bar'{,bak}" -prop_checkMvArguments4 = verifyNot checkMvArguments "mv \"$@\"" -prop_checkMvArguments5 = verifyNot checkMvArguments "mv -t foo bar" -prop_checkMvArguments6 = verifyNot checkMvArguments "mv --target-directory=foo bar" -prop_checkMvArguments7 = verifyNot checkMvArguments "mv --target-direc=foo bar" -prop_checkMvArguments8 = verifyNot checkMvArguments "mv --version" -prop_checkMvArguments9 = 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." @@ -952,9 +981,10 @@ checkLnArguments = CommandCheck (Basename "ln") $ missingDestination f f t = warn (getId t) 2226 "This ln has no destination. Check the arguments, or specify '.' explicitly." -prop_checkFindRedirections1 = verify checkFindRedirections "find . -exec echo {} > file \\;" -prop_checkFindRedirections2 = verifyNot checkFindRedirections "find . -exec echo {} \\; > file" -prop_checkFindRedirections3 = 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 @@ -969,17 +999,18 @@ checkFindRedirections = CommandCheck (Basename "find") f "Redirection applies to the find command itself. Rewrite to work per action (or move to end)." _ -> return () -prop_checkWhich = 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_checkSudoRedirect1 = verify checkSudoRedirect "sudo echo 3 > /proc/file" -prop_checkSudoRedirect2 = verify checkSudoRedirect "sudo cmd < input" -prop_checkSudoRedirect3 = verify checkSudoRedirect "sudo cmd >> file" -prop_checkSudoRedirect4 = verify checkSudoRedirect "sudo cmd &> file" -prop_checkSudoRedirect5 = verifyNot checkSudoRedirect "sudo cmd 2>&1" -prop_checkSudoRedirect6 = verifyNot checkSudoRedirect "sudo cmd 2> log" -prop_checkSudoRedirect7 = 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 @@ -1003,13 +1034,14 @@ checkSudoRedirect = CommandCheck (Basename "sudo") f warnAbout _ = return () special file = concat (oversimplify file) == "/dev/null" -prop_checkSudoArgs1 = verify checkSudoArgs "sudo cd /root" -prop_checkSudoArgs2 = verify checkSudoArgs "sudo export x=3" -prop_checkSudoArgs3 = verifyNot checkSudoArgs "sudo ls /usr/local/protected" -prop_checkSudoArgs4 = verifyNot checkSudoArgs "sudo ls && export x=3" -prop_checkSudoArgs5 = verifyNot checkSudoArgs "sudo echo ls" -prop_checkSudoArgs6 = verifyNot checkSudoArgs "sudo -n -u export ls" -prop_checkSudoArgs7 = 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 @@ -1023,9 +1055,10 @@ checkSudoArgs = CommandCheck (Basename "sudo") f -- This mess is why ShellCheck prefers not to know. parseOpts = getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:" -prop_checkSourceArgs1 = verify checkSourceArgs "#!/bin/sh\n. script arg" -prop_checkSourceArgs2 = verifyNot checkSourceArgs "#!/bin/sh\n. script" -prop_checkSourceArgs3 = verifyNot checkSourceArgs "#!/bin/bash\n. script arg" +-- | +-- >>> prop $ verify checkSourceArgs "#!/bin/sh\n. script arg" +-- >>> prop $ verifyNot checkSourceArgs "#!/bin/sh\n. script" +-- >>> prop $ verifyNot checkSourceArgs "#!/bin/bash\n. script arg" checkSourceArgs = CommandCheck (Exactly ".") f where f t = whenShell [Sh, Dash] $ @@ -1033,6 +1066,3 @@ checkSourceArgs = CommandCheck (Exactly ".") f (file:arg1:_) -> warn (getId arg1) 2240 $ "The dot command does not support arguments in sh/dash. Set them as variables." _ -> return () - -return [] -runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |]) diff --git a/src/ShellCheck/Checks/ShellSupport.hs b/src/ShellCheck/Checks/ShellSupport.hs index 1bc425f..6e4dd54 100644 --- a/src/ShellCheck/Checks/ShellSupport.hs +++ b/src/ShellCheck/Checks/ShellSupport.hs @@ -17,9 +17,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} -module ShellCheck.Checks.ShellSupport (checker , ShellCheck.Checks.ShellSupport.runTests) where +module ShellCheck.Checks.ShellSupport (checker) where import ShellCheck.AST import ShellCheck.ASTLib @@ -33,8 +32,6 @@ import Data.Char import Data.List import Data.Maybe import qualified Data.Map as Map -import Test.QuickCheck.All (forAllProperties) -import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess) data ForShell = ForShell [Shell] (Token -> Analysis) @@ -67,9 +64,10 @@ testChecker (ForShell _ t) = verify c s = producesComments (testChecker c) s == Just True verifyNot c s = producesComments (testChecker c) s == Just False -prop_checkForDecimals1 = verify checkForDecimals "((3.14*c))" -prop_checkForDecimals2 = verify checkForDecimals "foo[1.2]=bar" -prop_checkForDecimals3 = 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 @@ -80,62 +78,63 @@ checkForDecimals = ForShell [Sh, Dash, Bash] f f _ = return () -prop_checkBashisms = verify checkBashisms "while read a; do :; done < <(a)" -prop_checkBashisms2 = verify checkBashisms "[ foo -nt bar ]" -prop_checkBashisms3 = verify checkBashisms "echo $((i++))" -prop_checkBashisms4 = verify checkBashisms "rm !(*.hs)" -prop_checkBashisms5 = verify checkBashisms "source file" -prop_checkBashisms6 = verify checkBashisms "[ \"$a\" == 42 ]" -prop_checkBashisms7 = verify checkBashisms "echo ${var[1]}" -prop_checkBashisms8 = verify checkBashisms "echo ${!var[@]}" -prop_checkBashisms9 = verify checkBashisms "echo ${!var*}" -prop_checkBashisms10= verify checkBashisms "echo ${var:4:12}" -prop_checkBashisms11= verifyNot checkBashisms "echo ${var:-4}" -prop_checkBashisms12= verify checkBashisms "echo ${var//foo/bar}" -prop_checkBashisms13= verify checkBashisms "exec -c env" -prop_checkBashisms14= verify checkBashisms "echo -n \"Foo: \"" -prop_checkBashisms15= verify checkBashisms "let n++" -prop_checkBashisms16= verify checkBashisms "echo $RANDOM" -prop_checkBashisms17= verify checkBashisms "echo $((RANDOM%6+1))" -prop_checkBashisms18= verify checkBashisms "foo &> /dev/null" -prop_checkBashisms19= verify checkBashisms "foo > file*.txt" -prop_checkBashisms20= verify checkBashisms "read -ra foo" -prop_checkBashisms21= verify checkBashisms "[ -a foo ]" -prop_checkBashisms22= verifyNot checkBashisms "[ foo -a bar ]" -prop_checkBashisms23= verify checkBashisms "trap mything ERR INT" -prop_checkBashisms24= verifyNot checkBashisms "trap mything INT TERM" -prop_checkBashisms25= verify checkBashisms "cat < /dev/tcp/host/123" -prop_checkBashisms26= verify checkBashisms "trap mything ERR SIGTERM" -prop_checkBashisms27= verify checkBashisms "echo *[^0-9]*" -prop_checkBashisms28= verify checkBashisms "exec {n}>&2" -prop_checkBashisms29= verify checkBashisms "echo ${!var}" -prop_checkBashisms30= verify checkBashisms "printf -v '%s' \"$1\"" -prop_checkBashisms31= verify checkBashisms "printf '%q' \"$1\"" -prop_checkBashisms32= verifyNot checkBashisms "#!/bin/dash\n[ foo -nt bar ]" -prop_checkBashisms33= verify checkBashisms "#!/bin/sh\necho -n foo" -prop_checkBashisms34= verifyNot checkBashisms "#!/bin/dash\necho -n foo" -prop_checkBashisms35= verifyNot checkBashisms "#!/bin/dash\nlocal foo" -prop_checkBashisms36= verifyNot checkBashisms "#!/bin/dash\nread -p foo -r bar" -prop_checkBashisms37= verifyNot checkBashisms "HOSTNAME=foo; echo $HOSTNAME" -prop_checkBashisms38= verify checkBashisms "RANDOM=9; echo $RANDOM" -prop_checkBashisms39= verify checkBashisms "foo-bar() { true; }" -prop_checkBashisms40= verify checkBashisms "echo $(/dev/null" -prop_checkBashisms48= verifyNot checkBashisms "#!/bin/sh\necho $LINENO" -prop_checkBashisms49= verify checkBashisms "#!/bin/dash\necho $MACHTYPE" -prop_checkBashisms50= verify checkBashisms "#!/bin/sh\ncmd >& file" -prop_checkBashisms51= verifyNot checkBashisms "#!/bin/sh\ncmd 2>&1" -prop_checkBashisms52= verifyNot checkBashisms "#!/bin/sh\ncmd >&2" -prop_checkBashisms53= verifyNot checkBashisms "#!/bin/sh\nprintf -- -f\n" -prop_checkBashisms54= verify checkBashisms "#!/bin/sh\nfoo+=bar" -prop_checkBashisms55= verify checkBashisms "#!/bin/sh\necho ${@%foo}" -prop_checkBashisms56= 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 +316,9 @@ checkBashisms = ForShell [Sh, Dash] $ \t -> do Assignment (_, _, name, _) -> name == var _ -> False -prop_checkEchoSed1 = verify checkEchoSed "FOO=$(echo \"$cow\" | sed 's/foo/bar/g')" -prop_checkEchoSed2 = 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]) = @@ -344,10 +344,11 @@ checkEchoSed = ForShell [Bash, Ksh] f f _ = return () -prop_checkBraceExpansionVars1 = verify checkBraceExpansionVars "echo {1..$n}" -prop_checkBraceExpansionVars2 = verifyNot checkBraceExpansionVars "echo {1,3,$n}" -prop_checkBraceExpansionVars3 = verify checkBraceExpansionVars "eval echo DSC{0001..$n}.jpg" -prop_checkBraceExpansionVars4 = 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 @@ -372,12 +373,13 @@ checkBraceExpansionVars = ForShell [Bash] f return $ isJust cmd && fromJust cmd `isUnqualifiedCommand` "eval" -prop_checkMultiDimensionalArrays1 = verify checkMultiDimensionalArrays "foo[a][b]=3" -prop_checkMultiDimensionalArrays2 = verifyNot checkMultiDimensionalArrays "foo[a]=3" -prop_checkMultiDimensionalArrays3 = verify checkMultiDimensionalArrays "foo=( [a][b]=c )" -prop_checkMultiDimensionalArrays4 = verifyNot checkMultiDimensionalArrays "foo=( [a]=c )" -prop_checkMultiDimensionalArrays5 = verify checkMultiDimensionalArrays "echo ${foo[bar][baz]}" -prop_checkMultiDimensionalArrays6 = 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 = @@ -392,16 +394,17 @@ checkMultiDimensionalArrays = ForShell [Bash] f re = mkRegex "^\\[.*\\]\\[.*\\]" -- Fixme, this matches ${foo:- [][]} and such as well isMultiDim t = getBracedModifier (bracedString t) `matches` re -prop_checkPS11 = verify checkPS1Assignments "PS1='\\033[1;35m\\$ '" -prop_checkPS11a= verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '" -prop_checkPSf2 = verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" -prop_checkPS13 = verify checkPS1Assignments "PS1=$'\\x1b[c '" -prop_checkPS14 = verify checkPS1Assignments "PS1=$'\\e[3m; '" -prop_checkPS14a= verify checkPS1Assignments "export PS1=$'\\e[3m; '" -prop_checkPS15 = verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" -prop_checkPS16 = verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" -prop_checkPS17 = verifyNot checkPS1Assignments "PS1='e033x1B'" -prop_checkPS18 = 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 @@ -417,7 +420,3 @@ checkPS1Assignments = ForShell [Bash] f isJust $ matchRegex escapeRegex unenclosed enclosedRegex = mkRegex "\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager escapeRegex = mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033" - - -return [] -runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |]) diff --git a/src/ShellCheck/Parser.hs b/src/ShellCheck/Parser.hs index 172ef54..98bf17e 100644 --- a/src/ShellCheck/Parser.hs +++ b/src/ShellCheck/Parser.hs @@ -17,11 +17,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiWayIf #-} -module ShellCheck.Parser (parseScript, runTests) where +module ShellCheck.Parser (parseScript) where import ShellCheck.AST import ShellCheck.ASTLib @@ -48,7 +47,9 @@ import qualified Control.Monad.Reader as Mr import qualified Control.Monad.State as Ms import qualified Data.Map as Map -import Test.QuickCheck.All (quickCheckAll) +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 @@ -87,7 +88,8 @@ extglobStart = oneOf extglobStartChars unicodeDoubleQuotes = "\x201C\x201D\x2033\x2036" unicodeSingleQuotes = "\x2018\x2019" -prop_spacing = isOk spacing " \\\n # Comment" +-- | +-- >>> prop $ isOk spacing " \\\n # Comment" spacing = do x <- many (many1 linewhitespace <|> try (string "\\\n" >> return "")) optional readComment @@ -98,9 +100,10 @@ spacing1 = do when (null spacing) $ fail "Expected whitespace" return spacing -prop_allspacing = isOk allspacing "#foo" -prop_allspacing2 = isOk allspacing " #foo\n # bar\n#baz\n" -prop_allspacing3 = 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 +676,30 @@ readConditionContents single = readCondContents = readCondOr -prop_a1 = isOk readArithmeticContents " n++ + ++c" -prop_a2 = isOk readArithmeticContents "$N*4-(3,2)" -prop_a3 = isOk readArithmeticContents "n|=2<<1" -prop_a4 = isOk readArithmeticContents "n &= 2 **3" -prop_a5 = isOk readArithmeticContents "1 |= 4 && n >>= 4" -prop_a6 = isOk readArithmeticContents " 1 | 2 ||3|4" -prop_a7 = isOk readArithmeticContents "3*2**10" -prop_a8 = isOk readArithmeticContents "3" -prop_a9 = isOk readArithmeticContents "a^!-b" -prop_a10= isOk readArithmeticContents "! $?" -prop_a11= isOk readArithmeticContents "10#08 * 16#f" -prop_a12= isOk readArithmeticContents "\"$((3+2))\" + '37'" -prop_a13= isOk readArithmeticContents "foo[9*y+x]++" -prop_a14= isOk readArithmeticContents "1+`echo 2`" -prop_a15= isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4" -prop_a16= isOk readArithmeticContents "$foo$bar" -prop_a17= isOk readArithmeticContents "i<(0+(1+1))" -prop_a18= isOk readArithmeticContents "a?b:c" -prop_a19= isOk readArithmeticContents "\\\n3 +\\\n 2" -prop_a20= isOk readArithmeticContents "a ? b ? c : d : e" -prop_a21= isOk readArithmeticContents "a ? b : c ? d : e" -prop_a22= isOk readArithmeticContents "!!a" -prop_a23= 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 @@ -876,33 +880,34 @@ readArithmeticContents = -prop_readCondition = isOk readCondition "[ \\( a = b \\) -a \\( c = d \\) ]" -prop_readCondition2 = isOk readCondition "[[ (a = b) || (c = d) ]]" -prop_readCondition3 = isOk readCondition "[[ $c = [[:alpha:].~-] ]]" -prop_readCondition4 = isOk readCondition "[[ $c =~ *foo* ]]" -prop_readCondition5 = isOk readCondition "[[ $c =~ f( ]] )* ]]" -prop_readCondition5a = isOk readCondition "[[ $c =~ a(b) ]]" -prop_readCondition5b = isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]" -prop_readCondition6 = isOk readCondition "[[ $c =~ ^[yY]$ ]]" -prop_readCondition7 = isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]" -prop_readCondition8 = isOk readCondition "[[ $l =~ ogg|flac ]]" -prop_readCondition9 = isOk readCondition "[ foo -a -f bar ]" -prop_readCondition10 = isOk readCondition "[[\na == b\n||\nc == d ]]" -prop_readCondition10a= isOk readCondition "[[\na == b ||\nc == d ]]" -prop_readCondition10b= isOk readCondition "[[ a == b\n||\nc == d ]]" -prop_readCondition11 = isOk readCondition "[[ a == b ||\n c == d ]]" -prop_readCondition12 = isWarning readCondition "[ a == b \n -o c == d ]" -prop_readCondition13 = isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]" -prop_readCondition14 = isOk readCondition "[ foo '>' bar ]" -prop_readCondition15 = isOk readCondition "[ foo \">=\" bar ]" -prop_readCondition16 = isOk readCondition "[ foo \\< bar ]" -prop_readCondition17 = isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]" -prop_readCondition18 = isOk readCondition "[ ]" -prop_readCondition19 = isOk readCondition "[ '(' x \")\" ]" -prop_readCondition20 = isOk readCondition "[[ echo_rc -eq 0 ]]" -prop_readCondition21 = isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]" -prop_readCondition22 = isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]" -prop_readCondition23 = 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 @@ -940,12 +945,13 @@ readAnnotationPrefix = do many linewhitespace string "shellcheck" -prop_readAnnotation1 = isOk readAnnotation "# shellcheck disable=1234,5678\n" -prop_readAnnotation2 = isOk readAnnotation "# shellcheck disable=SC1234 disable=SC5678\n" -prop_readAnnotation3 = isOk readAnnotation "# shellcheck disable=SC1234 source=/dev/null disable=SC5678\n" -prop_readAnnotation4 = isWarning readAnnotation "# shellcheck cats=dogs disable=SC1234\n" -prop_readAnnotation5 = isOk readAnnotation "# shellcheck disable=SC2002 # All cats are precious\n" -prop_readAnnotation6 = 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 @@ -1002,18 +1008,19 @@ readAnyComment = do char '#' many $ noneOf "\r\n" -prop_readNormalWord = isOk readNormalWord "'foo'\"bar\"{1..3}baz$(lol)" -prop_readNormalWord2 = isOk readNormalWord "foo**(foo)!!!(@@(bar))" -prop_readNormalWord3 = isOk readNormalWord "foo#" -prop_readNormalWord4 = isOk readNormalWord "$\"foo\"$'foo\nbar'" -prop_readNormalWord5 = isWarning readNormalWord "${foo}}" -prop_readNormalWord6 = isOk readNormalWord "foo/{}" -prop_readNormalWord7 = isOk readNormalWord "foo\\\nbar" -prop_readNormalWord8 = isWarning readSubshell "(foo\\ \nbar)" -prop_readNormalWord9 = isOk readSubshell "(foo\\ ;\nbar)" -prop_readNormalWord10 = isWarning readNormalWord "\x201Chello\x201D" -prop_readNormalWord11 = isWarning readNormalWord "\x2018hello\x2019" -prop_readNormalWord12 = 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 @@ -1111,9 +1118,10 @@ readParamSubSpecialChar = do id <- endSpan start return $ T_ParamSubSpecialChar id x -prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)" -prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )" -prop_readProcSub3 = 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 @@ -1126,13 +1134,14 @@ readProcSub = called "process substitution" $ do id <- endSpan start return $ T_ProcSub id dir list -prop_readSingleQuoted = isOk readSingleQuoted "'foo bar'" -prop_readSingleQuoted2 = isWarning readSingleQuoted "'foo bar\\'" -prop_readSingleQuoted4 = isWarning readNormalWord "'it's" -prop_readSingleQuoted5 = isWarning readSimpleCommand "foo='bar\ncow 'arg" -prop_readSingleQuoted6 = isOk readSimpleCommand "foo='bar cow 'arg" -prop_readSingleQuoted7 = isOk readSingleQuoted "'foo\x201C\&bar'" -prop_readSingleQuoted8 = 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 @@ -1174,14 +1183,15 @@ readSingleQuotedPart = return [x] -prop_readBackTicked = isOk (readBackTicked False) "`ls *.mp3`" -prop_readBackTicked2 = isOk (readBackTicked False) "`grep \"\\\"\"`" -prop_readBackTicked3 = isWarning (readBackTicked False) "´grep \"\\\"\"´" -prop_readBackTicked4 = isOk readSimpleCommand "`echo foo\necho bar`" -prop_readBackTicked5 = isOk readSimpleCommand "echo `foo`bar" -prop_readBackTicked6 = isWarning readSimpleCommand "echo `foo\necho `bar" -prop_readBackTicked7 = isOk readSimpleCommand "`#inline comment`" -prop_readBackTicked8 = 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 @@ -1247,15 +1257,16 @@ parseForgettingContext alsoOnSuccess parser = do Ms.put c fail "" -prop_readDoubleQuoted = isOk readDoubleQuoted "\"Hello $FOO\"" -prop_readDoubleQuoted2 = isOk readDoubleQuoted "\"$'\"" -prop_readDoubleQuoted3 = isOk readDoubleQuoted "\"\x2018hello\x2019\"" -prop_readDoubleQuoted4 = isWarning readSimpleCommand "\"foo\nbar\"foo" -prop_readDoubleQuoted5 = isOk readSimpleCommand "lol \"foo\nbar\" etc" -prop_readDoubleQuoted6 = isOk readSimpleCommand "echo \"${ ls; }\"" -prop_readDoubleQuoted7 = isOk readSimpleCommand "echo \"${ ls;}bar\"" -prop_readDoubleQuoted8 = isWarning readDoubleQuoted "\"\x201Chello\x201D\"" -prop_readDoubleQuoted10 = 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 @@ -1308,14 +1319,15 @@ readNormalLiteral end = do id <- endSpan start return $ T_Literal id (concat s) -prop_readGlob1 = isOk readGlob "*" -prop_readGlob2 = isOk readGlob "[^0-9]" -prop_readGlob3 = isOk readGlob "[a[:alpha:]]" -prop_readGlob4 = isOk readGlob "[[:alnum:]]" -prop_readGlob5 = isOk readGlob "[^[:alpha:]1-9]" -prop_readGlob6 = isOk readGlob "[\\|]" -prop_readGlob7 = isOk readGlob "[^[]" -prop_readGlob8 = 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 @@ -1383,13 +1395,14 @@ readNormalEscaped = called "escaped char" $ do parseProblemAt pos ErrorC 1101 "Delete trailing spaces after \\ to break line (or use quotes for literal space)." -prop_readExtglob1 = isOk readExtglob "!(*.mp3)" -prop_readExtglob2 = isOk readExtglob "!(*.mp3|*.wmv)" -prop_readExtglob4 = isOk readExtglob "+(foo \\) bar)" -prop_readExtglob5 = isOk readExtglob "+(!(foo *(bar)))" -prop_readExtglob6 = isOk readExtglob "*(((||))|())" -prop_readExtglob7 = isOk readExtglob "*(<>)" -prop_readExtglob8 = 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 @@ -1465,14 +1478,15 @@ readGenericEscaped = do x <- anyChar return $ if x == '\n' then [] else ['\\', x] -prop_readBraced = isOk readBraced "{1..4}" -prop_readBraced2 = isOk readBraced "{foo,bar,\"baz lol\"}" -prop_readBraced3 = isOk readBraced "{1,\\},2}" -prop_readBraced4 = isOk readBraced "{1,{2,3}}" -prop_readBraced5 = isOk readBraced "{JP{,E}G,jp{,e}g}" -prop_readBraced6 = isOk readBraced "{foo,bar,$((${var}))}" -prop_readBraced7 = isNotOk readBraced "{}" -prop_readBraced8 = 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 = @@ -1512,9 +1526,10 @@ readDoubleQuotedDollar = do readDollarExp <|> readDollarLonely -prop_readDollarExpression1 = isOk readDollarExpression "$(((1) && 3))" -prop_readDollarExpression2 = isWarning readDollarExpression "$(((1)) && 3)" -prop_readDollarExpression3 = 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 @@ -1525,7 +1540,8 @@ readDollarExp = arithmetic <|> readDollarExpansion <|> readDollarBracket <|> rea arithmetic = readAmbiguous "$((" readDollarArithmetic readDollarExpansion (\pos -> parseNoteAt pos WarningC 1102 "Shells disambiguate $(( differently or not at all. For $(command substition), add space after $( . For $((arithmetics)), fix parsing errors.") -prop_readDollarSingleQuote = isOk readDollarSingleQuote "$'foo\\\'lol'" +-- | +-- >>> prop $ isOk readDollarSingleQuote "$'foo\\\'lol'" readDollarSingleQuote = called "$'..' expression" $ do start <- startSpan try $ string "$'" @@ -1534,7 +1550,8 @@ readDollarSingleQuote = called "$'..' expression" $ do id <- endSpan start return $ T_DollarSingleQuoted id str -prop_readDollarDoubleQuote = isOk readDollarDoubleQuote "$\"hello\"" +-- | +-- >>> prop $ isOk readDollarDoubleQuote "$\"hello\"" readDollarDoubleQuote = do lookAhead . try $ string "$\"" start <- startSpan @@ -1545,8 +1562,9 @@ readDollarDoubleQuote = do id <- endSpan start return $ T_DollarDoubleQuoted id x -prop_readDollarArithmetic = isOk readDollarArithmetic "$(( 3 * 4 +5))" -prop_readDollarArithmetic2 = 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 "$((") @@ -1565,7 +1583,8 @@ readDollarBracket = called "$[..] expression" $ do id <- endSpan start return (T_DollarBracket id c) -prop_readArithmeticExpression = isOk readArithmeticExpression "((a?b:c))" +-- | +-- >>> prop $ isOk readArithmeticExpression "((a?b:c))" readArithmeticExpression = called "((..)) command" $ do start <- startSpan try (string "((") @@ -1588,8 +1607,9 @@ readAmbiguous prefix expected alternative warner = do warner pos return t -prop_readDollarBraceCommandExpansion1 = isOk readDollarBraceCommandExpansion "${ ls; }" -prop_readDollarBraceCommandExpansion2 = isOk readDollarBraceCommandExpansion "${\nls\n}" +-- | +-- >>> prop $ isOk readDollarBraceCommandExpansion "${ ls; }" +-- >>> prop $ isOk readDollarBraceCommandExpansion "${\nls\n}" readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do start <- startSpan try $ do @@ -1601,10 +1621,11 @@ readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do id <- endSpan start return $ T_DollarBraceCommandExpansion id term -prop_readDollarBraced1 = isOk readDollarBraced "${foo//bar/baz}" -prop_readDollarBraced2 = isOk readDollarBraced "${foo/'{cow}'}" -prop_readDollarBraced3 = isOk readDollarBraced "${foo%%$(echo cow\\})}" -prop_readDollarBraced4 = 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 "${") @@ -1613,9 +1634,10 @@ readDollarBraced = called "parameter expansion" $ do id <- endSpan start return $ T_DollarBraced id word -prop_readDollarExpansion1= isOk readDollarExpansion "$(echo foo; ls\n)" -prop_readDollarExpansion2= isOk readDollarExpansion "$( )" -prop_readDollarExpansion3= 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 "$(") @@ -1624,12 +1646,12 @@ readDollarExpansion = called "command expansion" $ do id <- endSpan start return $ T_DollarExpansion id cmds -prop_readDollarVariable = isOk readDollarVariable "$@" -prop_readDollarVariable2 = isOk (readDollarVariable >> anyChar) "$?!" -prop_readDollarVariable3 = isWarning (readDollarVariable >> anyChar) "$10" -prop_readDollarVariable4 = isWarning (readDollarVariable >> string "[@]") "$arr[@]" -prop_readDollarVariable5 = 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 @@ -1678,25 +1700,26 @@ readDollarLonely = do n <- lookAhead (anyChar <|> (eof >> return '_')) return $ T_Literal id "$" -prop_readHereDoc = isOk readScript "cat << foo\nlol\ncow\nfoo" -prop_readHereDoc2 = isNotOk readScript "cat <<- EOF\n cow\n EOF" -prop_readHereDoc3 = isOk readScript "cat << foo\n$\"\nfoo" -prop_readHereDoc4 = isNotOk readScript "cat << foo\n`\nfoo" -prop_readHereDoc5 = isOk readScript "cat <<- !foo\nbar\n!foo" -prop_readHereDoc6 = isOk readScript "cat << foo\\ bar\ncow\nfoo bar" -prop_readHereDoc7 = isOk readScript "cat << foo\n\\$(f ())\nfoo" -prop_readHereDoc8 = isOk readScript "cat <>bar\netc\nfoo" -prop_readHereDoc9 = isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n" -prop_readHereDoc10= isOk readScript "if true; then cat << foo << bar; fi\nfoo\nbar\n" -prop_readHereDoc11= isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n" -prop_readHereDoc12= isOk readScript "cat << foo|cat\nbar\nfoo" -prop_readHereDoc13= isOk readScript "cat <<'#!'\nHello World\n#!\necho Done" -prop_readHereDoc14= isWarning readScript "cat << foo\nbar\nfoo \n" -prop_readHereDoc15= isWarning readScript "cat <>> 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 "<<" @@ -1864,7 +1887,8 @@ readIoDuplicate = try $ do return $ str ++ dash -prop_readIoFile = isOk readIoFile ">> \"$(date +%YYmmDD)\"" +-- | +-- >>> prop $ isOk readIoFile ">> \"$(date +%YYmmDD)\"" readIoFile = called "redirection" $ do start <- startSpan op <- readIoFileOp @@ -1884,13 +1908,14 @@ readIoSource = try $ do lookAhead $ void readIoFileOp <|> void (string "<<") return x -prop_readIoRedirect = isOk readIoRedirect "3>&2" -prop_readIoRedirect2 = isOk readIoRedirect "2> lol" -prop_readIoRedirect3 = isOk readIoRedirect "4>&-" -prop_readIoRedirect4 = isOk readIoRedirect "&> lol" -prop_readIoRedirect5 = isOk readIoRedirect "{foo}>&2" -prop_readIoRedirect6 = isOk readIoRedirect "{foo}<&-" -prop_readIoRedirect7 = 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 @@ -1902,7 +1927,8 @@ readIoRedirect = do readRedirectList = many1 readIoRedirect -prop_readHereString = isOk readHereString "<<< \"Hello $world\"" +-- | +-- >>> prop $ isOk readHereString "<<< \"Hello $world\"" readHereString = called "here string" $ do start <- startSpan try $ string "<<<" @@ -1921,11 +1947,12 @@ readNewlineList = "Unexpected start of line. If breaking lines, |/||/&& should be at the end of the previous one." readLineBreak = optional readNewlineList -prop_readSeparator1 = isWarning readScript "a &; b" -prop_readSeparator2 = isOk readScript "a & b" -prop_readSeparator3 = isWarning readScript "a & b" -prop_readSeparator4 = isWarning readScript "a > file; b" -prop_readSeparator5 = 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 "&>") @@ -1969,20 +1996,21 @@ readSeparator = end <- getPosition return ('\n', (start, end)) -prop_readSimpleCommand = isOk readSimpleCommand "echo test > file" -prop_readSimpleCommand2 = isOk readSimpleCommand "cmd &> file" -prop_readSimpleCommand3 = isOk readSimpleCommand "export foo=(bar baz)" -prop_readSimpleCommand4 = isOk readSimpleCommand "typeset -a foo=(lol)" -prop_readSimpleCommand5 = isOk readSimpleCommand "time if true; then echo foo; fi" -prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )" -prop_readSimpleCommand7 = isOk readSimpleCommand "\\ls" -prop_readSimpleCommand8 = isWarning readSimpleCommand "// Lol" -prop_readSimpleCommand9 = isWarning readSimpleCommand "/* Lolbert */" -prop_readSimpleCommand10 = isWarning readSimpleCommand "/**** Lolbert */" -prop_readSimpleCommand11 = isOk readSimpleCommand "/\\* foo" -prop_readSimpleCommand12 = isWarning readSimpleCommand "elsif foo" -prop_readSimpleCommand13 = isWarning readSimpleCommand "ElseIf foo" -prop_readSimpleCommand14 = 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 @@ -2108,9 +2136,10 @@ readSource t@(T_Redirecting _ _ (T_SimpleCommand cmdId _ (cmd:file:_))) = do readSource t = return t -prop_readPipeline = isOk readPipeline "! cat /etc/issue | grep -i ubuntu" -prop_readPipeline2 = isWarning readPipeline "!cat /etc/issue | grep -i ubuntu" -prop_readPipeline3 = 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 @@ -2120,9 +2149,10 @@ readPipeline = do <|> readPipeSequence -prop_readAndOr = isOk readAndOr "grep -i lol foo || exit 1" -prop_readAndOr1 = isOk readAndOr "# shellcheck disable=1\nfoo" -prop_readAndOr2 = 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 @@ -2150,7 +2180,8 @@ readTermOrNone = do eof return [] -prop_readTerm = isOk readTerm "time ( foo; bar; )" +-- | +-- >>> prop $ isOk readTerm "time ( foo; bar; )" readTerm = do allspacing m <- readAndOr @@ -2221,11 +2252,12 @@ skipAnnotationAndWarn = optional $ do parseProblem ErrorC 1126 "Place shellcheck directives before commands, not after." readAnyComment -prop_readIfClause = isOk readIfClause "if false; then foo; elif true; then stuff; more stuff; else cows; fi" -prop_readIfClause2 = isWarning readIfClause "if false; then; echo oo; fi" -prop_readIfClause3 = isWarning readIfClause "if false; then true; else; echo lol; fi" -prop_readIfClause4 = isWarning readIfClause "if false; then true; else if true; then echo lol; fi; fi" -prop_readIfClause5 = 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 @@ -2300,7 +2332,8 @@ ifNextToken parser action = try . lookAhead $ parser action -prop_readSubshell = 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 '(' @@ -2311,9 +2344,10 @@ readSubshell = called "explicit subshell" $ do id <- endSpan start return $ T_Subshell id list -prop_readBraceGroup = isOk readBraceGroup "{ a; b | c | d; e; }" -prop_readBraceGroup2 = isWarning readBraceGroup "{foo;}" -prop_readBraceGroup3 = 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 '{' @@ -2331,7 +2365,8 @@ readBraceGroup = called "brace group" $ do id <- endSpan start return $ T_BraceGroup id list -prop_readWhileClause = 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 @@ -2340,7 +2375,8 @@ readWhileClause = called "while loop" $ do id <- endSpan start return $ T_WhileExpression id condition statements -prop_readUntilClause = 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 @@ -2373,17 +2409,18 @@ readDoGroup kwId = do return commands -prop_readForClause = isOk readForClause "for f in *; do rm \"$f\"; done" -prop_readForClause3 = isOk readForClause "for f; do foo; done" -prop_readForClause4 = isOk readForClause "for((i=0; i<10; i++)); do echo $i; done" -prop_readForClause5 = isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \necho $i\ndone" -prop_readForClause6 = isOk readForClause "for ((;;))\ndo echo $i\ndone" -prop_readForClause7 = isOk readForClause "for ((;;)) do echo $i\ndone" -prop_readForClause8 = isOk readForClause "for ((;;)) ; do echo $i\ndone" -prop_readForClause9 = isOk readForClause "for i do true; done" -prop_readForClause10= isOk readForClause "for ((;;)) { true; }" -prop_readForClause12= isWarning readForClause "for $a in *; do echo \"$a\"; done" -prop_readForClause13= 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 @@ -2416,8 +2453,9 @@ readForClause = called "for loop" $ do group <- readDoGroup id return $ T_ForIn id name values group -prop_readSelectClause1 = isOk readSelectClause "select foo in *; do echo $foo; done" -prop_readSelectClause2 = 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 @@ -2446,11 +2484,12 @@ readInClause = do return things -prop_readCaseClause = isOk readCaseClause "case foo in a ) lol; cow;; b|d) fooo; esac" -prop_readCaseClause2 = isOk readCaseClause "case foo\n in * ) echo bar;; esac" -prop_readCaseClause3 = isOk readCaseClause "case foo\n in * ) echo bar & ;; esac" -prop_readCaseClause4 = isOk readCaseClause "case foo\n in *) echo bar ;& bar) foo; esac" -prop_readCaseClause5 = 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 @@ -2494,18 +2533,19 @@ readCaseSeparator = choice [ lookAhead (readLineBreak >> g_Esac) >> return CaseBreak ] -prop_readFunctionDefinition = isOk readFunctionDefinition "foo() { command foo --lol \"$@\"; }" -prop_readFunctionDefinition1 = isOk readFunctionDefinition "foo (){ command foo --lol \"$@\"; }" -prop_readFunctionDefinition4 = isWarning readFunctionDefinition "foo(a, b) { true; }" -prop_readFunctionDefinition5 = isOk readFunctionDefinition ":(){ :|:;}" -prop_readFunctionDefinition6 = isOk readFunctionDefinition "?(){ foo; }" -prop_readFunctionDefinition7 = isOk readFunctionDefinition "..(){ cd ..; }" -prop_readFunctionDefinition8 = isOk readFunctionDefinition "foo() (ls)" -prop_readFunctionDefinition9 = isOk readFunctionDefinition "function foo { true; }" -prop_readFunctionDefinition10= isOk readFunctionDefinition "function foo () { true; }" -prop_readFunctionDefinition11= isWarning readFunctionDefinition "function foo{\ntrue\n}" -prop_readFunctionDefinition12= isOk readFunctionDefinition "function []!() { true; }" -prop_readFunctionDefinition13= 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 @@ -2547,9 +2587,10 @@ readFunctionDefinition = called "function" $ do g_Rparen return () -prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }" -prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }" -prop_readCoProc3 = 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 @@ -2576,7 +2617,8 @@ readCoProc = called "coproc" $ do readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing) -prop_readCompoundCommand = isOk readCompoundCommand "{ echo foo; }>/dev/null" +-- | +-- >>> prop $ isOk readCompoundCommand "{ echo foo; }>/dev/null" readCompoundCommand = do cmd <- choice [ readBraceGroup, @@ -2668,24 +2710,26 @@ readLiteralForParser parser = do id <- endSpan start return $ T_Literal id str -prop_readAssignmentWord = isOk readAssignmentWord "a=42" -prop_readAssignmentWord2 = isOk readAssignmentWord "b=(1 2 3)" -prop_readAssignmentWord3 = isWarning readAssignmentWord "$b = 13" -prop_readAssignmentWord4 = isWarning readAssignmentWord "b = $(lol)" -prop_readAssignmentWord5 = isOk readAssignmentWord "b+=lol" -prop_readAssignmentWord6 = isWarning readAssignmentWord "b += (1 2 3)" -prop_readAssignmentWord7 = isOk readAssignmentWord "a[3$n'']=42" -prop_readAssignmentWord8 = isOk readAssignmentWord "a[4''$(cat foo)]=42" -prop_readAssignmentWord9 = isOk readAssignmentWord "IFS= " -prop_readAssignmentWord9a= isOk readAssignmentWord "foo=" -prop_readAssignmentWord9b= isOk readAssignmentWord "foo= " -prop_readAssignmentWord9c= isOk readAssignmentWord "foo= #bar" -prop_readAssignmentWord10= isWarning readAssignmentWord "foo$n=42" -prop_readAssignmentWord11= isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )" -prop_readAssignmentWord12= isOk readAssignmentWord "a[b <<= 3 + c]='thing'" -prop_readAssignmentWord13= isOk readAssignmentWord "var=( (1 2) (3 4) )" -prop_readAssignmentWord14= isOk readAssignmentWord "var=( 1 [2]=(3 4) )" -prop_readAssignmentWord15= 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 @@ -2879,13 +2923,14 @@ readKeyword = choice [ g_Then, g_Else, g_Elif, g_Fi, g_Do, g_Done, g_Esac, g_Rbr ifParse p t f = (lookAhead (try p) >> t) <|> f -prop_readShebang1 = isOk readShebang "#!/bin/sh\n" -prop_readShebang2 = isWarning readShebang "!# /bin/sh\n" -prop_readShebang3 = isNotOk readShebang "#shellcheck shell=/bin/sh\n" -prop_readShebang4 = isWarning readShebang "! /bin/sh" -prop_readShebang5 = isWarning readShebang "\n#!/bin/sh" -prop_readShebang6 = isWarning readShebang " # Copyright \n!#/bin/bash" -prop_readShebang7 = 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 @@ -2968,11 +3013,12 @@ verifyEof = eof <|> choice [ try (lookAhead p) action -prop_readScript1 = isOk readScriptFile "#!/bin/bash\necho hello world\n" -prop_readScript2 = isWarning readScriptFile "#!/bin/bash\r\necho hello world\n" -prop_readScript3 = isWarning readScriptFile "#!/bin/bash\necho hello\xA0world" -prop_readScript4 = isWarning readScriptFile "#!/usr/bin/perl\nfoo=(" -prop_readScript5 = 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 @@ -3295,7 +3341,3 @@ tryWithErrors parser = do endInput <- getInput endState <- getState return (result, endPos, endInput, endState) - -return [] -runTests = $quickCheckAll - diff --git a/stack.yaml b/stack.yaml index d39cada..ab2abbd 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,35 +1,3 @@ -# This file was automatically generated by stack init -# For more information, see: https://docs.haskellstack.org/en/stable/yaml_configuration/ - -# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) -resolver: lts-8.5 - -# Local packages, usually specified by relative directory name +resolver: lts-12.9 packages: - '.' -# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) -extra-deps: [] - -# Override default flag values for local packages and extra-deps -flags: {} - -# Extra package databases containing global packages -extra-package-dbs: [] - -# Control whether we use the GHC we find on the path -# system-ghc: true - -# Require a specific version of stack, using version ranges -# require-stack-version: -any # Default -# require-stack-version: >= 1.0.0 - -# Override the architecture used by stack, especially useful on Windows -# arch: i386 -# arch: x86_64 - -# Extra directories used by stack for building -# extra-include-dirs: [/path/to/dir] -# extra-lib-dirs: [/path/to/dir] - -# Allow a newer minor version of GHC than the snapshot specifies -# compiler-check: newer-minor diff --git a/striptests b/striptests index 8281f78..fc3db44 100755 --- a/striptests +++ b/striptests @@ -1,78 +1,2 @@ #!/usr/bin/env bash -# This file strips all unit tests from ShellCheck, removing -# the dependency on QuickCheck and Template Haskell and -# reduces the binary size considerably. -set -o pipefail - -sponge() { - local data - data="$(cat)" - printf '%s\n' "$data" > "$1" -} - -modify() { - if ! "${@:2}" < "$1" | sponge "$1" - then - { - printf 'Failed to modify %s: ' "$1" - printf '%q ' "${@:2}" - printf '\n' - } >&2 - exit 1 - fi -} - -detestify() { - printf '%s\n' '-- AUTOGENERATED from ShellCheck by striptests. Do not modify.' - awk ' - BEGIN { - state = 0; - } - - /LANGUAGE TemplateHaskell/ { next; } - /^import.*Test\./ { next; } - - /^module/ { - sub(/,[^,)]*runTests/, ""); - } - - # Delete tests - /^prop_/ { state = 1; next; } - - # ..and any blank lines following them. - state == 1 && /^ / { next; } - - # Template Haskell marker - /^return / { - exit; - } - - { state = 0; print; } - ' -} - - - -if [[ ! -e 'ShellCheck.cabal' ]] -then - echo "Run me from the ShellCheck directory." >&2 - exit 1 -fi - -if [[ -d '.git' ]] && ! git diff --exit-code > /dev/null 2>&1 -then - echo "You have local changes! These may be overwritten." >&2 - exit 2 -fi - -modify 'ShellCheck.cabal' sed -e ' - /QuickCheck/d - /^test-suite/{ s/.*//; q; } - ' - -find . -name '.git' -prune -o -type f -name '*.hs' -print | - while IFS= read -r file - do - modify "$file" detestify - done - +# This file was deprecated by the doctest build. diff --git a/test/doctests.hs b/test/doctests.hs new file mode 100644 index 0000000..bbf7787 --- /dev/null +++ b/test/doctests.hs @@ -0,0 +1,12 @@ +module Main where + +import Build_doctests (flags, pkgs, module_sources) +import Data.Foldable (traverse_) +import Test.DocTest (doctest) + +main :: IO () +main = do + traverse_ putStrLn args + doctest args + where + args = flags ++ pkgs ++ module_sources diff --git a/test/shellcheck.hs b/test/shellcheck.hs deleted file mode 100644 index 6106d9a..0000000 --- a/test/shellcheck.hs +++ /dev/null @@ -1,24 +0,0 @@ -module Main where - -import Control.Monad -import System.Exit -import qualified ShellCheck.Checker -import qualified ShellCheck.Analytics -import qualified ShellCheck.AnalyzerLib -import qualified ShellCheck.Parser -import qualified ShellCheck.Checks.Commands -import qualified ShellCheck.Checks.ShellSupport - -main = do - putStrLn "Running ShellCheck tests..." - results <- sequence [ - ShellCheck.Checker.runTests, - ShellCheck.Checks.Commands.runTests, - ShellCheck.Checks.ShellSupport.runTests, - ShellCheck.Analytics.runTests, - ShellCheck.AnalyzerLib.runTests, - ShellCheck.Parser.runTests - ] - if and results - then exitSuccess - else exitFailure