diff options
author | Vidar Holen <spam@vidarholen.net> | 2018-12-16 18:33:21 -0800 |
---|---|---|
committer | Vidar Holen <spam@vidarholen.net> | 2018-12-16 18:33:21 -0800 |
commit | 21462b11b31765619c5decb588269e889e314f64 (patch) | |
tree | 3ec6309fc4f4eb858240b4275dd9bc21ae7c2144 | |
parent | 88aef838f127f8a6ddc767f0d6b6b2fd22db5a24 (diff) | |
parent | 75949fe51ef1be58e6ed929c8acd6c5d13cc3ec1 (diff) |
Merge branch 'doctest-new-build' of https://github.com/phadej/shellcheck into phadej-doctest-new-builddoctest
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Dockerfile | 47 | ||||
-rw-r--r-- | Setup.hs | 55 | ||||
-rw-r--r-- | ShellCheck.cabal | 45 | ||||
-rwxr-xr-x | quickrun | 7 | ||||
-rwxr-xr-x | quicktest | 39 | ||||
-rw-r--r-- | src/ShellCheck/Analytics.hs | 1521 | ||||
-rw-r--r-- | src/ShellCheck/AnalyzerLib.hs | 133 | ||||
-rw-r--r-- | src/ShellCheck/Checker.hs | 244 | ||||
-rw-r--r-- | src/ShellCheck/Checks/Commands.hs | 500 | ||||
-rw-r--r-- | src/ShellCheck/Checks/ShellSupport.hs | 177 | ||||
-rw-r--r-- | src/ShellCheck/Parser.hs | 604 | ||||
-rw-r--r-- | stack.yaml | 34 | ||||
-rwxr-xr-x | striptests | 78 | ||||
-rw-r--r-- | test/doctests.hs | 12 | ||||
-rw-r--r-- | test/shellcheck.hs | 24 |
16 files changed, 1833 insertions, 1690 deletions
@@ -13,6 +13,9 @@ cabal-dev cabal.sandbox.config cabal.config .stack-work +dist-newstyle/ +.ghc.environment.* +cabal.project.local ### Snap ### /snap/.snapcraft/ @@ -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/ @@ -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 @@ -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. @@ -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 <https://www.gnu.org/licenses/>. -} -{-# 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]}" |