summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVidar Holen <spam@vidarholen.net>2018-12-16 18:33:21 -0800
committerVidar Holen <spam@vidarholen.net>2018-12-16 18:33:21 -0800
commit21462b11b31765619c5decb588269e889e314f64 (patch)
tree3ec6309fc4f4eb858240b4275dd9bc21ae7c2144
parent88aef838f127f8a6ddc767f0d6b6b2fd22db5a24 (diff)
parent75949fe51ef1be58e6ed929c8acd6c5d13cc3ec1 (diff)
Merge branch 'doctest-new-build' of https://github.com/phadej/shellcheck into phadej-doctest-new-builddoctest
-rw-r--r--.gitignore3
-rw-r--r--Dockerfile47
-rw-r--r--Setup.hs55
-rw-r--r--ShellCheck.cabal45
-rwxr-xr-xquickrun7
-rwxr-xr-xquicktest39
-rw-r--r--src/ShellCheck/Analytics.hs1521
-rw-r--r--src/ShellCheck/AnalyzerLib.hs133
-rw-r--r--src/ShellCheck/Checker.hs244
-rw-r--r--src/ShellCheck/Checks/Commands.hs500
-rw-r--r--src/ShellCheck/Checks/ShellSupport.hs177
-rw-r--r--src/ShellCheck/Parser.hs604
-rw-r--r--stack.yaml34
-rwxr-xr-xstriptests78
-rw-r--r--test/doctests.hs12
-rw-r--r--test/shellcheck.hs24
16 files changed, 1833 insertions, 1690 deletions
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 <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]}"