39 Commits

Author SHA1 Message Date
Vidar Holen
21462b11b3 Merge branch 'doctest-new-build' of https://github.com/phadej/shellcheck into phadej-doctest-new-build 2018-12-16 18:33:21 -08:00
Vidar Holen
88aef838f1 SC1068 (var = x) now alternatively suggests quoting (fixes #1412) 2018-12-16 15:45:52 -08:00
Vidar Holen
3d61b73e91 Be more specific about why you should read the wiki page 2018-12-16 15:14:29 -08:00
Vidar Holen
138080bdc7 Fix infinite loop on annotations for SC2188 (fixes #1413) 2018-12-16 14:42:19 -08:00
Vidar Holen
5b3f17c29d Allow tests to access token positions for fixes 2018-12-16 13:17:59 -08:00
Vidar Holen
b47e083ee3 Fix 'does not support multiple targets at once' error 2018-12-16 10:15:32 -08:00
Vidar Holen
3cba76dc7d Update CHANGELOG with new release and autofix merge 2018-12-09 15:01:08 -08:00
Vidar Holen
eb588f62f6 Enable autofix support. It's still preliminary. 2018-12-09 15:01:08 -08:00
Vidar Holen
bcd13614eb Improve Fix memory usage 2018-12-09 15:01:08 -08:00
Vidar Holen
a8376a09a9 Minor renaming and output fixes 2018-12-09 15:01:08 -08:00
Ng Zhi An
5ed89d2241 Change definition of Replacement, add ToJSON instance for it 2018-12-09 15:01:08 -08:00
Ng Zhi An
4a87d2a3de Expose token positions in params, use that to construct fixes 2018-12-09 15:01:08 -08:00
Ng Zhi An
41613babd9 Prototype fix 2018-12-09 15:01:08 -08:00
Vidar Holen
cb57b4a74f Stable version 0.6.0
This release is dedicated to Factorio. If this is how much fun it is to
build factories and oppress natives, then history makes a lot of sense.
2018-12-02 19:08:06 -08:00
Vidar Holen
e3f0243c0e Add 'striptests' script to Cabal package 2018-12-02 19:08:06 -08:00
Vidar Holen
66b5f13c6f Make wiki links fit in 80 columns 2018-12-02 19:08:06 -08:00
Vidar Holen
a7a404a5a8 Fill in missing bits in CHANGELOG 2018-12-02 14:49:04 -08:00
Vidar Holen
0761f5c923 Merge pull request #1397 from ngzhian/man-en-dash
Disable smart typography extension for markdown input
2018-12-02 13:56:14 -08:00
Vidar Holen
b55149b22d Add man page instructions (fixes #1347) 2018-12-02 12:29:42 -08:00
Ng Zhi An
4097bb5154 Disable smart typography extension for markdown input
Fixes #1392
2018-11-29 23:18:20 -08:00
Vidar Holen
1b207b3d43 Preemptively fix possible '-- |' breakage 2018-11-26 20:43:15 -08:00
Vidar Holen
135b4aa485 Add stack builds to distro test 2018-11-26 20:41:29 -08:00
Vidar Holen
cb76951ad2 Add warnings for 'exit' similar to 'return' (fixes #1388) 2018-11-24 23:05:40 -08:00
Vidar Holen
705e476e4c Merge pull request #1389 from romanzolotarev/patch-1
Add OpenBSD to "Installing" section of README.md
2018-11-15 19:58:30 +11:00
Roman Zolotarev
e705552c97 Update README.html
Add OpenBSD to "Installing" section.
2018-11-15 08:49:26 +00:00
Vidar Holen
198aa4fc3d Merge pull request #1383 from l2dy/master
Fix typo in CHANGELOG
2018-11-08 11:13:55 +08:00
Zero King
f4044fbcc7 Fix typo in CHANGELOG 2018-11-08 03:07:52 +00:00
Vidar Holen
2827b35696 SC2240: Warn about . script args.. in sh/dash (fixes #1373) 2018-11-07 18:04:18 -08:00
Vidar Holen
de95c376ea Merge pull request #1380 from PeterDaveHello/Update-Dockerfile
Update Docker build-only image to Ubuntu 18.04
2018-11-08 08:34:21 +08:00
Peter Dave Hello
5e1b1e010a Update Docker build-only image to Ubuntu 18.04
Ref:
> Ubuntu 17.10 (Artful Aardvark) End of Life reached on July 19 2018
https://fridge.ubuntu.com/2018/07/19/ubuntu-17-10-artful-aardvark-end-of-life-reached-on-july-19-2018/
2018-11-02 13:47:22 +08:00
Vidar Holen
620c9c2023 Also warn about glob matching with [ a != b* ] (fixes #1374) 2018-11-01 04:47:44 -07:00
Vidar Holen
359b1467a2 Work around snap's old cabal + new snapcraft proxy. 2018-10-24 21:33:42 -07:00
Oleg Grenrus
75949fe51e Change prop> to >>> prop $
As doctest doesn't make QuickCheck related magic, but only
evaluates expressions: We are fast.
2018-10-17 19:29:10 +03:00
Oleg Grenrus
d510a3ef6c Guard quickcheck.1 regeneration
Without this guard it will be regenerate on each run,
and cabal new-build considers whole package dirty.
2018-09-10 19:44:06 +03:00
Oleg Grenrus
5516596b26 Fix quicktest, add note to it and quickrun 2018-09-10 19:41:15 +03:00
Oleg Grenrus
9e7539c10b Use native codegen in docker build, results in smaller binary 2018-09-10 19:09:28 +03:00
Oleg Grenrus
a5a7b332f1 Use LLVM + split-sections 2018-09-10 19:09:28 +03:00
Oleg Grenrus
a68e3aeb26 Striptests is not necessary anymore 2018-09-10 19:09:28 +03:00
Oleg Grenrus
259b1a5dc6 Run tests as doctests 2018-09-10 19:09:28 +03:00
24 changed files with 1956 additions and 1703 deletions

View File

@@ -1,7 +1,7 @@
#### For bugs #### For bugs
- Rule Id (if any, e.g. SC1000): - Rule Id (if any, e.g. SC1000):
- My shellcheck version (`shellcheck --version` or "online"): - My shellcheck version (`shellcheck --version` or "online"):
- [ ] I read the issue's wiki page, e.g. https://github.com/koalaman/shellcheck/wiki/SC2086 - [ ] The rule's wiki page does not already cover this (e.g. https://shellcheck.net/wiki/SC2086)
- [ ] I tried on shellcheck.net and verified that this is still a problem on the latest commit - [ ] I tried on shellcheck.net and verified that this is still a problem on the latest commit
#### For new checks and feature suggestions #### For new checks and feature suggestions

3
.gitignore vendored
View File

@@ -13,6 +13,9 @@ cabal-dev
cabal.sandbox.config cabal.sandbox.config
cabal.config cabal.config
.stack-work .stack-work
dist-newstyle/
.ghc.environment.*
cabal.project.local
### Snap ### ### Snap ###
/snap/.snapcraft/ /snap/.snapcraft/

14
.snapsquid.conf Normal file
View File

@@ -0,0 +1,14 @@
# In 2015, cabal-install had a http bug triggered when proxies didn't keep
# the connection open. This version made it into Ubuntu Xenial as used by
# Snapcraft. In June 2018, Snapcraft's proxy started triggering this bug.
#
# https://bugs.launchpad.net/launchpad-buildd/+bug/1797809
#
# Workaround: add more proxy
visible_hostname localhost
http_port 8888
cache_peer 10.10.10.1 parent 8222 0 no-query default
cache_peer_domain localhost !.internal
http_access allow all

View File

@@ -1,17 +1,30 @@
## ??? ## Since previous release
### Added
- Preliminary support for fix suggestions
## v0.6.0 - 2018-12-02
### Added ### Added
- Command line option --severity/-S for filtering by minimum severity - Command line option --severity/-S for filtering by minimum severity
- Command line option --wiki-link-count/-W for showing wiki links - Command line option --wiki-link-count/-W for showing wiki links
- SC2152/SC2151: Warn about bad `exit` values like `1234` and `"foo"`
- SC2236/SC2237: Suggest -n/-z instead of ! -z/-n - SC2236/SC2237: Suggest -n/-z instead of ! -z/-n
- SC2238: Warn when redirecting to a known command name, e.g. ls > rm - SC2238: Warn when redirecting to a known command name, e.g. ls > rm
- SC2239: Warn if the shebang is not an absolute path, e.g. #!bin/sh - SC2239: Warn if the shebang is not an absolute path, e.g. #!bin/sh
- SC2240: Warn when passing additional arguments to dot (.) in sh/dash
- SC1133: Better diagnostics when starting a line with |/||/&& - SC1133: Better diagnostics when starting a line with |/||/&&
### Changed ### Changed
- Most warnings now have useful end positions - Most warnings now have useful end positions
- SC1117 about unknown double-quoted escape sequences has been retired - SC1117 about unknown double-quoted escape sequences has been retired
### Fixed ### Fixed
- SC2021 no longer triggers for equivalence classes like '[=e=]' - SC2021 no longer triggers for equivalence classes like `[=e=]`
- SC2221/SC2222 no longer mistriggers on fall-through case branches - SC2221/SC2222 no longer mistriggers on fall-through case branches
- SC2081 about glob matches in `[ .. ]` now also triggers for `!=`
- SC2086 no longer warns about spaces in `$#`
- SC2164 no longer suggests subshells for `cd ..; cmd; cd ..`
- `read -a` is now correctly considered an array assignment
- SC2039 no longer warns about LINENO now that it's POSIX
## v0.5.0 - 2018-05-31 ## v0.5.0 - 2018-05-31
### Added ### Added

View File

@@ -1,22 +1,55 @@
# Build-only image # Build-only image
FROM ubuntu:17.10 AS build FROM ubuntu:18.04 AS build
USER root USER root
WORKDIR /opt/shellCheck WORKDIR /opt/shellCheck
# Install OS deps # Install OS deps, including GHC from HVR-PPA
RUN apt-get update && apt-get install -y ghc cabal-install # 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 # Install Haskell deps
# (This is a separate copy/run so that source changes don't require rebuilding) # (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 ./ 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 source and build it
COPY LICENSE Setup.hs shellcheck.hs ./ COPY LICENSE Setup.hs shellcheck.hs shellcheck.1.md ./
COPY src src COPY src src
RUN cabal build Paths_ShellCheck && \ COPY test test
ghc -optl-static -optl-pthread -isrc -idist/build/autogen --make shellcheck -split-sections -optc-Wl,--gc-sections -optlo-Os && \ # This SED is the only "nastyness" we have to do
strip --strip-all shellcheck # 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 && \ RUN mkdir -p /out/bin && \
cp shellcheck /out/bin/ cp shellcheck /out/bin/

View File

@@ -133,6 +133,10 @@ On OS X with homebrew:
brew install shellcheck brew install shellcheck
On OpenBSD:
pkg_add shellcheck
On openSUSE On openSUSE
zypper in ShellCheck zypper in ShellCheck
@@ -168,6 +172,11 @@ Alternatively, you can download pre-compiled binaries for the latest release her
or see the [storage bucket listing](https://shellcheck.storage.googleapis.com/index.html) for checksums, older versions and the latest daily builds. or see the [storage bucket listing](https://shellcheck.storage.googleapis.com/index.html) for checksums, older versions and the latest daily builds.
Distro packages already come with a `man` page. If you are building from source, it can be installed with:
pandoc -s -t man shellcheck.1.md -o shellcheck.1
sudo mv shellcheck.1 /usr/share/man/man1
## Travis CI ## Travis CI
Travis CI has now integrated ShellCheck by default, so you don't need to manually install it. Travis CI has now integrated ShellCheck by default, so you don't need to manually install it.

View File

@@ -1,3 +1,8 @@
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wall #-}
module Main (main) where
import Distribution.PackageDescription ( import Distribution.PackageDescription (
HookedBuildInfo, HookedBuildInfo,
emptyHookedBuildInfo ) emptyHookedBuildInfo )
@@ -9,12 +14,42 @@ import Distribution.Simple (
import Distribution.Simple.Setup ( SDistFlags ) import Distribution.Simple.Setup ( SDistFlags )
import System.Process ( system ) 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 main = defaultMainWithHooks myHooks
where where
myHooks = simpleUserHooks { preSDist = myPreSDist } myHooks = simpleUserHooks { preSDist = myPreSDist }
#endif
-- | This hook will be executed before e.g. @cabal sdist@. It runs -- | 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 -- pandoc to create the man page from shellcheck.1.md. If the pandoc
-- command is not found, this will fail with an error message: -- command is not found, this will fail with an error message:
@@ -27,10 +62,20 @@ main = defaultMainWithHooks myHooks
-- --
myPreSDist :: Args -> SDistFlags -> IO HookedBuildInfo myPreSDist :: Args -> SDistFlags -> IO HookedBuildInfo
myPreSDist _ _ = do myPreSDist _ _ = do
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
makeManPage = do
putStrLn "Building the man page (shellcheck.1) with pandoc..." putStrLn "Building the man page (shellcheck.1) with pandoc..."
putStrLn pandoc_cmd putStrLn pandoc_cmd
result <- system pandoc_cmd result <- system pandoc_cmd
putStrLn $ "pandoc exited with " ++ show result putStrLn $ "pandoc exited with " ++ show result
return emptyHookedBuildInfo
where
pandoc_cmd = "pandoc -s -t man shellcheck.1.md -o shellcheck.1" pandoc_cmd = "pandoc -s -t man shellcheck.1.md -o shellcheck.1"

View File

@@ -1,5 +1,5 @@
Name: ShellCheck Name: ShellCheck
Version: 0.5.0 Version: 0.6.0
Synopsis: Shell script analysis tool Synopsis: Shell script analysis tool
License: GPL-3 License: GPL-3
License-file: LICENSE License-file: LICENSE
@@ -28,13 +28,13 @@ Extra-Source-Files:
shellcheck.1.md shellcheck.1.md
-- built with a cabal sdist hook -- built with a cabal sdist hook
shellcheck.1 shellcheck.1
-- tests
test/shellcheck.hs
custom-setup custom-setup
setup-depends: setup-depends:
base >= 4 && <5, base >= 4 && <5,
directory >= 1.2 && <1.4,
process >= 1.0 && <1.7, process >= 1.0 && <1.7,
cabal-doctest >= 1.0.6 && <1.1,
Cabal >= 1.10 && <2.5 Cabal >= 1.10 && <2.5
source-repository head source-repository head
@@ -58,7 +58,6 @@ library
mtl >= 2.2.1, mtl >= 2.2.1,
parsec, parsec,
regex-tdfa, regex-tdfa,
QuickCheck >= 2.7.4,
-- When cabal supports it, move this to setup-depends: -- When cabal supports it, move this to setup-depends:
process process
exposed-modules: exposed-modules:
@@ -96,23 +95,23 @@ executable shellcheck
directory, directory,
mtl >= 2.2.1, mtl >= 2.2.1,
parsec >= 3.0, parsec >= 3.0,
QuickCheck >= 2.7.4,
regex-tdfa regex-tdfa
main-is: shellcheck.hs main-is: shellcheck.hs
test-suite test-shellcheck -- Marker to add flags for static linking
type: exitcode-stdio-1.0 -- STATIC
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
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

View File

@@ -3,3 +3,10 @@
# This allows testing changes without recompiling. # This allows testing changes without recompiling.
runghc -isrc -idist/build/autogen shellcheck.hs "$@" 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.

View File

@@ -1,22 +1,21 @@
#!/usr/bin/env bash #!/bin/bash
# quicktest runs the ShellCheck unit tests in an interpreted mode. # shellcheck disable=SC2091
# This allows running tests without compiling, which can be faster.
# 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. # 'cabal test' remains the source of truth.
( $(find dist -type f -name doctests)
var=$(echo 'liftM and $ sequence [
ShellCheck.Analytics.runTests # Note: if you have build the project with new-build
,ShellCheck.Parser.runTests #
,ShellCheck.Checker.runTests # % cabal new-build -w ghc-8.4.3 --enable-tests
,ShellCheck.Checks.Commands.runTests #
,ShellCheck.Checks.ShellSupport.runTests # and have cabal-plan installed (e.g. with cabal new-install cabal-plan),
,ShellCheck.AnalyzerLib.runTests # then you can quicktest with
]' | tr -d '\n' | cabal repl 2>&1 | tee /dev/stderr) #
if [[ $var == *$'\nTrue'* ]] # % $(cabal-plan list-bin doctests)
then #
exit 0 # Once the test executable exists, we can simply run it to perform doctests
else # which use GHCi under the hood.
grep -C 3 -e "Fail" -e "Tracing" <<< "$var"
exit 1
fi
) 2>&1

View File

@@ -37,9 +37,16 @@ parts:
source: ./ source: ./
build-packages: build-packages:
- cabal-install - cabal-install
- squid3
build: | build: |
# See comments in .snapsquid.conf
[ "$http_proxy" ] && {
squid3 -f .snapsquid.conf
export http_proxy="http://localhost:8888"
sleep 3
}
cabal sandbox init cabal sandbox init
cabal update cabal update || cat /var/log/squid/*
cabal install -j cabal install -j
install: | install: |
install -d $SNAPCRAFT_PART_INSTALL/usr/bin install -d $SNAPCRAFT_PART_INSTALL/usr/bin

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}
module ShellCheck.AnalyzerLib where module ShellCheck.AnalyzerLib where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
import ShellCheck.Data import ShellCheck.Data
@@ -36,12 +34,13 @@ import Control.Monad.State
import Control.Monad.Writer import Control.Monad.Writer
import Data.Char import Data.Char
import Data.List import Data.List
import qualified Data.Map as Map
import Data.Maybe import Data.Maybe
import Data.Semigroup import Data.Semigroup
import qualified Data.Map as Map
import Test.QuickCheck.All (forAllProperties) prop :: Bool -> IO ()
import Test.QuickCheck.Test (maxSuccess, quickCheckWithResult, stdArgs) prop False = putStrLn "FAIL"
prop True = return ()
type Analysis = AnalyzerM () type Analysis = AnalyzerM ()
type AnalyzerM a = RWS Parameters [TokenComment] Cache a type AnalyzerM a = RWS Parameters [TokenComment] Cache a
@@ -85,7 +84,7 @@ data Parameters = Parameters {
shellTypeSpecified :: Bool, -- True if shell type was forced via flags shellTypeSpecified :: Bool, -- True if shell type was forced via flags
rootNode :: Token, -- The root node of the AST rootNode :: Token, -- The root node of the AST
tokenPositions :: Map.Map Id (Position, Position) -- map from token id to start and end position tokenPositions :: Map.Map Id (Position, Position) -- map from token id to start and end position
} } deriving (Show)
-- TODO: Cache results of common AST ops here -- TODO: Cache results of common AST ops here
data Cache = Cache {} data Cache = Cache {}
@@ -112,11 +111,12 @@ data DataSource =
data VariableState = Dead Token String | Alive deriving (Show) data VariableState = Dead Token String | Alive deriving (Show)
defaultSpec root = spec { defaultSpec pr = spec {
asShellType = Nothing, asShellType = Nothing,
asCheckSourced = False, asCheckSourced = False,
asExecutionMode = Executed asExecutionMode = Executed,
} where spec = newAnalysisSpec root asTokenPositions = prTokenPositions pr
} where spec = newAnalysisSpec (fromJust $ prRoot pr)
pScript s = pScript s =
let let
@@ -124,13 +124,14 @@ pScript s =
psFilename = "script", psFilename = "script",
psScript = s psScript = s
} }
in prRoot . runIdentity $ parseScript (mockedSystemInterface []) pSpec in runIdentity $ parseScript (mockedSystemInterface []) pSpec
-- For testing. If parsed, returns whether there are any comments -- For testing. If parsed, returns whether there are any comments
producesComments :: Checker -> String -> Maybe Bool producesComments :: Checker -> String -> Maybe Bool
producesComments c s = do producesComments c s = do
root <- pScript s let pr = pScript s
let spec = defaultSpec root prRoot pr
let spec = defaultSpec pr
let params = makeParameters spec let params = makeParameters spec
return . not . null $ runChecker params c return . not . null $ runChecker params c
@@ -214,16 +215,16 @@ containsLastpipe root =
_ -> False _ -> False
prop_determineShell0 = determineShell (fromJust $ pScript "#!/bin/sh") == Sh -- |
prop_determineShell1 = determineShell (fromJust $ pScript "#!/usr/bin/env ksh") == Ksh -- >>> prop $ determineShellTest "#!/bin/sh" == Sh
prop_determineShell2 = determineShell (fromJust $ pScript "") == Bash -- >>> prop $ determineShellTest "#!/usr/bin/env ksh" == Ksh
prop_determineShell3 = determineShell (fromJust $ pScript "#!/bin/sh -e") == Sh -- >>> prop $ determineShellTest "" == Bash
prop_determineShell4 = determineShell (fromJust $ pScript -- >>> prop $ determineShellTest "#!/bin/sh -e" == Sh
"#!/bin/ksh\n#shellcheck shell=sh\nfoo") == Sh -- >>> prop $ determineShellTest "#!/bin/ksh\n#shellcheck shell=sh\nfoo" == Sh
prop_determineShell5 = determineShell (fromJust $ pScript -- >>> prop $ determineShellTest "#shellcheck shell=sh\nfoo" == Sh
"#shellcheck shell=sh\nfoo") == Sh -- >>> prop $ determineShellTest "#! /bin/sh" == Sh
prop_determineShell6 = determineShell (fromJust $ pScript "#! /bin/sh") == Sh -- >>> prop $ determineShellTest "#! /bin/ash" == Dash
prop_determineShell7 = determineShell (fromJust $ pScript "#! /bin/ash") == Dash determineShellTest = determineShell . fromJust . prRoot . pScript
determineShell t = fromMaybe Bash $ do determineShell t = fromMaybe Bash $ do
shellString <- foldl mplus Nothing $ getCandidates t shellString <- foldl mplus Nothing $ getCandidates t
shellForExecutable shellString shellForExecutable shellString
@@ -665,10 +666,11 @@ getIndexReferences s = fromMaybe [] $ do
where where
re = mkRegex "(\\[.*\\])" re = mkRegex "(\\[.*\\])"
prop_getOffsetReferences1 = getOffsetReferences ":bar" == ["bar"] -- |
prop_getOffsetReferences2 = getOffsetReferences ":bar:baz" == ["bar", "baz"] -- >>> prop $ getOffsetReferences ":bar" == ["bar"]
prop_getOffsetReferences3 = getOffsetReferences "[foo]:bar" == ["bar"] -- >>> prop $ getOffsetReferences ":bar:baz" == ["bar", "baz"]
prop_getOffsetReferences4 = getOffsetReferences "[foo]:bar:baz" == ["bar", "baz"] -- >>> prop $ getOffsetReferences "[foo]:bar" == ["bar"]
-- >>> prop $ getOffsetReferences "[foo]:bar:baz" == ["bar", "baz"]
getOffsetReferences mods = fromMaybe [] $ do getOffsetReferences mods = fromMaybe [] $ do
-- if mods start with [, then drop until ] -- if mods start with [, then drop until ]
match <- matchRegex re mods match <- matchRegex re mods
@@ -743,9 +745,15 @@ isUnqualifiedCommand token str = isCommandMatch token (== str)
isCommandMatch token matcher = fromMaybe False $ isCommandMatch token matcher = fromMaybe False $
fmap matcher (getCommandName token) fmap matcher (getCommandName token)
-- |
-- Does this regex look like it was intended as a glob? -- 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 :: String -> Bool
isConfusedGlobRegex ('*':_) = True isConfusedGlobRegex ('*':_) = True
isConfusedGlobRegex [x,'*'] | x /= '\\' = True isConfusedGlobRegex [x,'*'] | x /= '\\' = True
@@ -755,9 +763,10 @@ isVariableStartChar x = x == '_' || isAsciiLower x || isAsciiUpper x
isVariableChar x = isVariableStartChar x || isDigit x isVariableChar x = isVariableStartChar x || isDigit x
variableNameRegex = mkRegex "[_a-zA-Z][_a-zA-Z0-9]*" variableNameRegex = mkRegex "[_a-zA-Z][_a-zA-Z0-9]*"
prop_isVariableName1 = isVariableName "_fo123" -- |
prop_isVariableName2 = not $ isVariableName "4" -- >>> prop $ isVariableName "_fo123"
prop_isVariableName3 = not $ isVariableName "test: " -- >>> prop $ not $ isVariableName "4"
-- >>> prop $ not $ isVariableName "test: "
isVariableName (x:r) = isVariableStartChar x && all isVariableChar r isVariableName (x:r) = isVariableStartChar x && all isVariableChar r
isVariableName _ = False isVariableName _ = False
@@ -766,27 +775,28 @@ getVariablesFromLiteralToken token =
-- Try to get referenced variables from a literal string like "$foo" -- Try to get referenced variables from a literal string like "$foo"
-- Ignores tons of cases like arithmetic evaluation and array indices. -- Ignores tons of cases like arithmetic evaluation and array indices.
prop_getVariablesFromLiteral1 = -- >>> prop $ getVariablesFromLiteral "$foo${bar//a/b}$BAZ" == ["foo", "bar", "BAZ"]
getVariablesFromLiteral "$foo${bar//a/b}$BAZ" == ["foo", "bar", "BAZ"]
getVariablesFromLiteral string = getVariablesFromLiteral string =
map (!! 0) $ matchAllSubgroups variableRegex string map (!! 0) $ matchAllSubgroups variableRegex string
where where
variableRegex = mkRegex "\\$\\{?([A-Za-z0-9_]+)" variableRegex = mkRegex "\\$\\{?([A-Za-z0-9_]+)"
-- |
-- Get the variable name from an expansion like ${var:-foo} -- Get the variable name from an expansion like ${var:-foo}
prop_getBracedReference1 = getBracedReference "foo" == "foo" --
prop_getBracedReference2 = getBracedReference "#foo" == "foo" -- >>> prop $ getBracedReference "foo" == "foo"
prop_getBracedReference3 = getBracedReference "#" == "#" -- >>> prop $ getBracedReference "#foo" == "foo"
prop_getBracedReference4 = getBracedReference "##" == "#" -- >>> prop $ getBracedReference "#" == "#"
prop_getBracedReference5 = getBracedReference "#!" == "!" -- >>> prop $ getBracedReference "##" == "#"
prop_getBracedReference6 = getBracedReference "!#" == "#" -- >>> prop $ getBracedReference "#!" == "!"
prop_getBracedReference7 = getBracedReference "!foo#?" == "foo" -- >>> prop $ getBracedReference "!#" == "#"
prop_getBracedReference8 = getBracedReference "foo-bar" == "foo" -- >>> prop $ getBracedReference "!foo#?" == "foo"
prop_getBracedReference9 = getBracedReference "foo:-bar" == "foo" -- >>> prop $ getBracedReference "foo-bar" == "foo"
prop_getBracedReference10= getBracedReference "foo: -1" == "foo" -- >>> prop $ getBracedReference "foo:-bar" == "foo"
prop_getBracedReference11= getBracedReference "!os*" == "" -- >>> prop $ getBracedReference "foo: -1" == "foo"
prop_getBracedReference12= getBracedReference "!os?bar**" == "" -- >>> prop $ getBracedReference "!os*" == ""
prop_getBracedReference13= getBracedReference "foo[bar]" == "foo" -- >>> prop $ getBracedReference "!os?bar**" == ""
-- >>> prop $ getBracedReference "foo[bar]" == "foo"
getBracedReference s = fromMaybe s $ getBracedReference s = fromMaybe s $
nameExpansion s `mplus` takeName noPrefix `mplus` getSpecial noPrefix `mplus` getSpecial s nameExpansion s `mplus` takeName noPrefix `mplus` getSpecial noPrefix `mplus` getSpecial s
where where
@@ -809,9 +819,10 @@ getBracedReference s = fromMaybe s $
return "" return ""
nameExpansion _ = Nothing nameExpansion _ = Nothing
prop_getBracedModifier1 = getBracedModifier "foo:bar:baz" == ":bar:baz" -- |
prop_getBracedModifier2 = getBracedModifier "!var:-foo" == ":-foo" -- >>> prop $ getBracedModifier "foo:bar:baz" == ":bar:baz"
prop_getBracedModifier3 = getBracedModifier "foo[bar]" == "[bar]" -- >>> prop $ getBracedModifier "!var:-foo" == ":-foo"
-- >>> prop $ getBracedModifier "foo[bar]" == "[bar]"
getBracedModifier s = fromMaybe "" . listToMaybe $ do getBracedModifier s = fromMaybe "" . listToMaybe $ do
let var = getBracedReference s let var = getBracedReference s
a <- dropModifier s a <- dropModifier s
@@ -828,10 +839,13 @@ getBracedModifier s = fromMaybe "" . listToMaybe $ do
-- Run an action in a Maybe (or do nothing). -- Run an action in a Maybe (or do nothing).
-- Example: -- Example:
--
-- @
-- potentially $ do -- potentially $ do
-- s <- getLiteralString cmd -- s <- getLiteralString cmd
-- guard $ s `elem` ["--recursive", "-r"] -- guard $ s `elem` ["--recursive", "-r"]
-- return $ warn .. "Something something recursive" -- return $ warn .. "Something something recursive"
-- @
potentially :: Monad m => Maybe (m ()) -> m () potentially :: Monad m => Maybe (m ()) -> m ()
potentially = fromMaybe (return ()) potentially = fromMaybe (return ())
@@ -916,6 +930,3 @@ getOpts flagTokenizer string cmd = process flags
else do else do
more <- process rest2 more <- process rest2
return $ (flag1, token1) : more return $ (flag1, token1) : more
return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])

View File

@@ -17,8 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE TemplateHaskell #-} module ShellCheck.Checker (checkScript) where
module ShellCheck.Checker (checkScript, ShellCheck.Checker.runTests) where
import ShellCheck.Interface import ShellCheck.Interface
import ShellCheck.Parser import ShellCheck.Parser
@@ -35,8 +34,6 @@ import qualified System.IO
import Prelude hiding (readFile) import Prelude hiding (readFile)
import Control.Monad import Control.Monad
import Test.QuickCheck.All
tokenToPosition startMap t = fromMaybe fail $ do tokenToPosition startMap t = fromMaybe fail $ do
span <- Map.lookup (tcId t) startMap span <- Map.lookup (tcId t) startMap
return $ newPositionedComment { return $ newPositionedComment {
@@ -125,111 +122,132 @@ checkRecursive includes src =
csCheckSourced = True csCheckSourced = True
} }
prop_findsParseIssue = check "echo \"$12\"" == [1037] -- | Dummy binding for doctest to run
--
prop_commentDisablesParseIssue1 = -- >>> check "echo \"$12\""
null $ check "#shellcheck disable=SC1037\necho \"$12\"" -- [1037]
prop_commentDisablesParseIssue2 = --
null $ check "#shellcheck disable=SC1037\n#lol\necho \"$12\"" -- >>> check "#shellcheck disable=SC1037\necho \"$12\""
-- []
prop_findsAnalysisIssue = --
check "echo $1" == [2086] -- >>> check "#shellcheck disable=SC1037\n#lol\necho \"$12\""
prop_commentDisablesAnalysisIssue1 = -- []
null $ check "#shellcheck disable=SC2086\necho $1" --
prop_commentDisablesAnalysisIssue2 = -- >>> check "echo $1"
null $ check "#shellcheck disable=SC2086\n#lol\necho $1" -- [2086]
--
prop_optionDisablesIssue1 = -- >>> check "#shellcheck disable=SC2086\necho $1"
null $ getErrors -- []
(mockedSystemInterface []) --
emptyCheckSpec { -- >>> check "#shellcheck disable=SC2086\n#lol\necho $1"
csScript = "echo $1", -- []
csExcludedWarnings = [2148, 2086] --
} -- >>> :{
-- getErrors
prop_optionDisablesIssue2 = -- (mockedSystemInterface [])
null $ getErrors -- emptyCheckSpec {
(mockedSystemInterface []) -- csScript = "echo $1",
emptyCheckSpec { -- csExcludedWarnings = [2148, 2086]
csScript = "echo \"$10\"", -- }
csExcludedWarnings = [2148, 1037] -- :}
} -- []
--
prop_wontParseBadShell = -- >>> :{
[1071] == check "#!/usr/bin/python\ntrue $1\n" -- getErrors
-- (mockedSystemInterface [])
prop_optionDisablesBadShebang = -- emptyCheckSpec {
null $ getErrors -- csScript = "echo \"$10\"",
(mockedSystemInterface []) -- csExcludedWarnings = [2148, 1037]
emptyCheckSpec { -- }
csScript = "#!/usr/bin/python\ntrue\n", -- :}
csShellTypeOverride = Just Sh -- []
} --
-- >>> check "#!/usr/bin/python\ntrue $1\n"
prop_annotationDisablesBadShebang = -- [1071]
[] == check "#!/usr/bin/python\n# shellcheck shell=sh\ntrue\n" --
-- >>> :{
-- getErrors
prop_canParseDevNull = -- (mockedSystemInterface [])
[] == check "source /dev/null" -- emptyCheckSpec {
-- csScript = "#!/usr/bin/python\ntrue\n",
prop_failsWhenNotSourcing = -- csShellTypeOverride = Just Sh
[1091, 2154] == check "source lol; echo \"$bar\"" -- }
-- :}
prop_worksWhenSourcing = -- []
null $ checkWithIncludes [("lib", "bar=1")] "source lib; echo \"$bar\"" --
-- >>> check "#!/usr/bin/python\n# shellcheck shell=sh\ntrue\n"
prop_worksWhenDotting = -- []
null $ checkWithIncludes [("lib", "bar=1")] ". lib; echo \"$bar\"" --
-- >>> check "source /dev/null"
prop_noInfiniteSourcing = -- []
[] == checkWithIncludes [("lib", "source lib")] "source lib" --
-- >>> check "source lol; echo \"$bar\""
prop_canSourceBadSyntax = -- [1091,2154]
[1094, 2086] == checkWithIncludes [("lib", "for f; do")] "source lib; echo $1" --
-- >>> checkWithIncludes [("lib", "bar=1")] "source lib; echo \"$bar\""
prop_cantSourceDynamic = -- []
[1090] == checkWithIncludes [("lib", "")] ". \"$1\"" --
-- >>> checkWithIncludes [("lib", "bar=1")] ". lib; echo \"$bar\""
prop_cantSourceDynamic2 = -- []
[1090] == checkWithIncludes [("lib", "")] "source ~/foo" --
-- >>> checkWithIncludes [("lib", "source lib")] "source lib"
prop_canSourceDynamicWhenRedirected = -- []
null $ checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\"" --
-- >>> checkWithIncludes [("lib", "for f; do")] "source lib; echo $1"
prop_recursiveAnalysis = -- [1094,2086]
[2086] == checkRecursive [("lib", "echo $1")] "source lib" --
-- >>> checkWithIncludes [("lib", "")] ". \"$1\""
prop_recursiveParsing = -- [1090]
[1037] == checkRecursive [("lib", "echo \"$10\"")] "source lib" --
-- >>> checkWithIncludes [("lib", "")] "source ~/foo"
prop_sourceDirectiveDoesntFollowFile = -- [1090]
null $ checkWithIncludes --
[("foo", "source bar"), ("bar", "baz=3")] -- >>> checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\""
"#shellcheck source=foo\n. \"$1\"; echo \"$baz\"" -- []
--
prop_filewideAnnotationBase = [2086] == check "#!/bin/sh\necho $1" -- >>> checkRecursive [("lib", "echo $1")] "source lib"
prop_filewideAnnotation1 = null $ -- [2086]
check "#!/bin/sh\n# shellcheck disable=2086\necho $1" --
prop_filewideAnnotation2 = null $ -- >>> checkRecursive [("lib", "echo \"$10\"")] "source lib"
check "#!/bin/sh\n# shellcheck disable=2086\ntrue\necho $1" -- [1037]
prop_filewideAnnotation3 = null $ --
check "#!/bin/sh\n#unrelated\n# shellcheck disable=2086\ntrue\necho $1" -- >>> checkWithIncludes [("foo", "source bar"), ("bar", "baz=3")] "#shellcheck source=foo\n. \"$1\"; echo \"$baz\""
prop_filewideAnnotation4 = null $ -- []
check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1" --
prop_filewideAnnotation5 = null $ -- >>> check "#!/bin/sh\necho $1"
check "#!/bin/sh\n\n\n\n#shellcheck disable=2086\ntrue\necho $1" -- [2086]
prop_filewideAnnotation6 = null $ --
check "#shellcheck shell=sh\n#unrelated\n#shellcheck disable=2086\ntrue\necho $1" -- >>> check "#!/bin/sh\n# shellcheck disable=2086\necho $1"
prop_filewideAnnotation7 = null $ -- []
check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1" --
-- >>> check "#!/bin/sh\n# shellcheck disable=2086\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" -- >>> check "#!/bin/sh\n#unrelated\n# shellcheck disable=2086\ntrue\necho $1"
-- []
prop_sourcePartOfOriginalScript = -- #1181: -x disabled posix warning for 'source' --
2039 `elem` checkWithIncludes [("./saywhat.sh", "echo foo")] "#!/bin/sh\nsource ./saywhat.sh" -- >>> check "#!/bin/sh\n# shellcheck disable=2086\n#unrelated\ntrue\necho $1"
-- []
return [] --
runTests = $quickCheckAll -- >>> 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 = ()

View File

@@ -17,11 +17,9 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
-- This module contains checks that examine specific commands by name. -- 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.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
@@ -37,8 +35,6 @@ import Data.Char
import Data.List import Data.List
import Data.Maybe import Data.Maybe
import qualified Data.Map.Strict as Map 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 data CommandName = Exactly String | Basename String
deriving (Eq, Ord) deriving (Eq, Ord)
@@ -46,7 +42,6 @@ data CommandName = Exactly String | Basename String
data CommandCheck = data CommandCheck =
CommandCheck CommandName (Token -> Analysis) CommandCheck CommandName (Token -> Analysis)
verify :: CommandCheck -> String -> Bool verify :: CommandCheck -> String -> Bool
verify f s = producesComments (getChecker [f]) s == Just True verify f s = producesComments (getChecker [f]) s == Just True
verifyNot f s = producesComments (getChecker [f]) s == Just False verifyNot f s = producesComments (getChecker [f]) s == Just False
@@ -61,6 +56,7 @@ commandChecks = [
,checkGrepRe ,checkGrepRe
,checkTrapQuotes ,checkTrapQuotes
,checkReturn ,checkReturn
,checkExit
,checkFindExecWithSingleArgument ,checkFindExecWithSingleArgument
,checkUnusedEchoEscapes ,checkUnusedEchoEscapes
,checkInjectableFindSh ,checkInjectableFindSh
@@ -92,6 +88,7 @@ commandChecks = [
,checkWhich ,checkWhich
,checkSudoRedirect ,checkSudoRedirect
,checkSudoArgs ,checkSudoArgs
,checkSourceArgs
] ]
buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis) buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
@@ -128,20 +125,21 @@ getChecker list = Checker {
checker :: Parameters -> Checker checker :: Parameters -> Checker
checker params = getChecker commandChecks checker params = getChecker commandChecks
prop_checkTr1 = verify checkTr "tr [a-f] [A-F]" -- |
prop_checkTr2 = verify checkTr "tr 'a-z' 'A-Z'" -- >>> prop $ verify checkTr "tr [a-f] [A-F]"
prop_checkTr2a= verify checkTr "tr '[a-z]' '[A-Z]'" -- >>> prop $ verify checkTr "tr 'a-z' 'A-Z'"
prop_checkTr3 = verifyNot checkTr "tr -d '[:lower:]'" -- >>> prop $ verify checkTr "tr '[a-z]' '[A-Z]'"
prop_checkTr3a= verifyNot checkTr "tr -d '[:upper:]'" -- >>> prop $ verifyNot checkTr "tr -d '[:lower:]'"
prop_checkTr3b= verifyNot checkTr "tr -d '|/_[:upper:]'" -- >>> prop $ verifyNot checkTr "tr -d '[:upper:]'"
prop_checkTr4 = verifyNot checkTr "ls [a-z]" -- >>> prop $ verifyNot checkTr "tr -d '|/_[:upper:]'"
prop_checkTr5 = verify checkTr "tr foo bar" -- >>> prop $ verifyNot checkTr "ls [a-z]"
prop_checkTr6 = verify checkTr "tr 'hello' 'world'" -- >>> prop $ verify checkTr "tr foo bar"
prop_checkTr8 = verifyNot checkTr "tr aeiou _____" -- >>> prop $ verify checkTr "tr 'hello' 'world'"
prop_checkTr9 = verifyNot checkTr "a-z n-za-m" -- >>> prop $ verifyNot checkTr "tr aeiou _____"
prop_checkTr10= verifyNot checkTr "tr --squeeze-repeats rl lr" -- >>> prop $ verifyNot checkTr "a-z n-za-m"
prop_checkTr11= verifyNot checkTr "tr abc '[d*]'" -- >>> prop $ verifyNot checkTr "tr --squeeze-repeats rl lr"
prop_checkTr12= verifyNot checkTr "tr '[=e=]' 'e'" -- >>> prop $ verifyNot checkTr "tr abc '[d*]'"
-- >>> prop $ verifyNot checkTr "tr '[=e=]' 'e'"
checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments) checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments)
where where
f w | isGlob w = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme? f w | isGlob w = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme?
@@ -162,9 +160,10 @@ checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments)
let relevant = filter isAlpha s let relevant = filter isAlpha s
in relevant /= nub relevant in relevant /= nub relevant
prop_checkFindNameGlob1 = verify checkFindNameGlob "find / -name *.php" -- |
prop_checkFindNameGlob2 = verify checkFindNameGlob "find / -type f -ipath *(foo)" -- >>> prop $ verify checkFindNameGlob "find / -name *.php"
prop_checkFindNameGlob3 = verifyNot 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 checkFindNameGlob = CommandCheck (Basename "find") (f . arguments) where
acceptsGlob (Just s) = s `elem` [ "-ilname", "-iname", "-ipath", "-iregex", "-iwholename", "-lname", "-name", "-path", "-regex", "-wholename" ] acceptsGlob (Just s) = s `elem` [ "-ilname", "-iname", "-ipath", "-iregex", "-iwholename", "-lname", "-name", "-path", "-regex", "-wholename" ]
acceptsGlob _ = False acceptsGlob _ = False
@@ -177,10 +176,11 @@ checkFindNameGlob = CommandCheck (Basename "find") (f . arguments) where
f (b:r) f (b:r)
prop_checkNeedlessExpr = verify checkNeedlessExpr "foo=$(expr 3 + 2)" -- |
prop_checkNeedlessExpr2 = verify checkNeedlessExpr "foo=`echo \\`expr 3 + 2\\``" -- >>> prop $ verify checkNeedlessExpr "foo=$(expr 3 + 2)"
prop_checkNeedlessExpr3 = verifyNot checkNeedlessExpr "foo=$(expr foo : regex)" -- >>> prop $ verify checkNeedlessExpr "foo=`echo \\`expr 3 + 2\\``"
prop_checkNeedlessExpr4 = verifyNot checkNeedlessExpr "foo=$(expr foo \\< regex)" -- >>> prop $ verifyNot checkNeedlessExpr "foo=$(expr foo : regex)"
-- >>> prop $ verifyNot checkNeedlessExpr "foo=$(expr foo \\< regex)"
checkNeedlessExpr = CommandCheck (Basename "expr") f where checkNeedlessExpr = CommandCheck (Basename "expr") f where
f t = f t =
when (all (`notElem` exceptions) (words $ arguments t)) $ when (all (`notElem` exceptions) (words $ arguments t)) $
@@ -191,21 +191,22 @@ checkNeedlessExpr = CommandCheck (Basename "expr") f where
words = mapMaybe getLiteralString words = mapMaybe getLiteralString
prop_checkGrepRe1 = verify checkGrepRe "cat foo | grep *.mp3" -- |
prop_checkGrepRe2 = verify checkGrepRe "grep -Ev cow*test *.mp3" -- >>> prop $ verify checkGrepRe "cat foo | grep *.mp3"
prop_checkGrepRe3 = verify checkGrepRe "grep --regex=*.mp3 file" -- >>> prop $ verify checkGrepRe "grep -Ev cow*test *.mp3"
prop_checkGrepRe4 = verifyNot checkGrepRe "grep foo *.mp3" -- >>> prop $ verify checkGrepRe "grep --regex=*.mp3 file"
prop_checkGrepRe5 = verifyNot checkGrepRe "grep-v --regex=moo *" -- >>> prop $ verifyNot checkGrepRe "grep foo *.mp3"
prop_checkGrepRe6 = verifyNot checkGrepRe "grep foo \\*.mp3" -- >>> prop $ verifyNot checkGrepRe "grep-v --regex=moo *"
prop_checkGrepRe7 = verify checkGrepRe "grep *foo* file" -- >>> prop $ verifyNot checkGrepRe "grep foo \\*.mp3"
prop_checkGrepRe8 = verify checkGrepRe "ls | grep foo*.jpg" -- >>> prop $ verify checkGrepRe "grep *foo* file"
prop_checkGrepRe9 = verifyNot checkGrepRe "grep '[0-9]*' file" -- >>> prop $ verify checkGrepRe "ls | grep foo*.jpg"
prop_checkGrepRe10= verifyNot checkGrepRe "grep '^aa*' file" -- >>> prop $ verifyNot checkGrepRe "grep '[0-9]*' file"
prop_checkGrepRe11= verifyNot checkGrepRe "grep --include=*.png foo" -- >>> prop $ verifyNot checkGrepRe "grep '^aa*' file"
prop_checkGrepRe12= verifyNot checkGrepRe "grep -F 'Foo*' file" -- >>> prop $ verifyNot checkGrepRe "grep --include=*.png foo"
prop_checkGrepRe13= verifyNot checkGrepRe "grep -- -foo bar*" -- >>> prop $ verifyNot checkGrepRe "grep -F 'Foo*' file"
prop_checkGrepRe14= verifyNot checkGrepRe "grep -e -foo bar*" -- >>> prop $ verifyNot checkGrepRe "grep -- -foo bar*"
prop_checkGrepRe15= verifyNot checkGrepRe "grep --regex -foo bar*" -- >>> prop $ verifyNot checkGrepRe "grep -e -foo bar*"
-- >>> prop $ verifyNot checkGrepRe "grep --regex -foo bar*"
checkGrepRe = CommandCheck (Basename "grep") check where checkGrepRe = CommandCheck (Basename "grep") check where
check cmd = f cmd (arguments cmd) check cmd = f cmd (arguments cmd)
@@ -256,10 +257,11 @@ checkGrepRe = CommandCheck (Basename "grep") check where
contra = mkRegex "[^a-zA-Z1-9]\\*|[][^$+\\\\]" contra = mkRegex "[^a-zA-Z1-9]\\*|[][^$+\\\\]"
prop_checkTrapQuotes1 = verify checkTrapQuotes "trap \"echo $num\" INT" -- |
prop_checkTrapQuotes1a= verify checkTrapQuotes "trap \"echo `ls`\" INT" -- >>> prop $ verify checkTrapQuotes "trap \"echo $num\" INT"
prop_checkTrapQuotes2 = verifyNot checkTrapQuotes "trap 'echo $num' INT" -- >>> prop $ verify checkTrapQuotes "trap \"echo `ls`\" INT"
prop_checkTrapQuotes3 = verify checkTrapQuotes "trap \"echo $((1+num))\" EXIT DEBUG" -- >>> prop $ verifyNot checkTrapQuotes "trap 'echo $num' INT"
-- >>> prop $ verify checkTrapQuotes "trap \"echo $((1+num))\" EXIT DEBUG"
checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where
f (x:_) = checkTrap x f (x:_) = checkTrap x
f _ = return () f _ = return ()
@@ -273,22 +275,37 @@ checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where
checkExpansions _ = return () checkExpansions _ = return ()
prop_checkReturn1 = verifyNot checkReturn "return" -- |
prop_checkReturn2 = verifyNot checkReturn "return 1" -- >>> prop $ verifyNot checkReturn "return"
prop_checkReturn3 = verifyNot checkReturn "return $var" -- >>> prop $ verifyNot checkReturn "return 1"
prop_checkReturn4 = verifyNot checkReturn "return $((a|b))" -- >>> prop $ verifyNot checkReturn "return $var"
prop_checkReturn5 = verify checkReturn "return -1" -- >>> prop $ verifyNot checkReturn "return $((a|b))"
prop_checkReturn6 = verify checkReturn "return 1000" -- >>> prop $ verify checkReturn "return -1"
prop_checkReturn7 = verify checkReturn "return 'hello world'" -- >>> prop $ verify checkReturn "return 1000"
checkReturn = CommandCheck (Exactly "return") (f . arguments) -- >>> 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 $ 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."))
returnOrExit multi invalid = (f . arguments)
where where
f (first:second:_) = f (first:second:_) =
err (getId second) 2151 multi (getId first)
"Only one integer 0-255 can be returned. Use stdout for other data."
f [value] = f [value] =
when (isInvalid $ literal value) $ when (isInvalid $ literal value) $
err (getId value) 2152 invalid (getId value)
"Can only return 0-255. Other data should be written to stdout."
f _ = return () f _ = return ()
isInvalid s = s == "" || any (not . isDigit) s || length s > 5 isInvalid s = s == "" || any (not . isDigit) s || length s > 5
@@ -302,9 +319,10 @@ checkReturn = CommandCheck (Exactly "return") (f . arguments)
lit _ = return "WTF" lit _ = return "WTF"
prop_checkFindExecWithSingleArgument1 = verify checkFindExecWithSingleArgument "find . -exec 'cat {} | wc -l' \\;" -- |
prop_checkFindExecWithSingleArgument2 = verify checkFindExecWithSingleArgument "find . -execdir 'cat {} | wc -l' +" -- >>> prop $ verify checkFindExecWithSingleArgument "find . -exec 'cat {} | wc -l' \\;"
prop_checkFindExecWithSingleArgument3 = verifyNot checkFindExecWithSingleArgument "find . -exec wc -l {} \\;" -- >>> prop $ verify checkFindExecWithSingleArgument "find . -execdir 'cat {} | wc -l' +"
-- >>> prop $ verifyNot checkFindExecWithSingleArgument "find . -exec wc -l {} \\;"
checkFindExecWithSingleArgument = CommandCheck (Basename "find") (f . arguments) checkFindExecWithSingleArgument = CommandCheck (Basename "find") (f . arguments)
where where
f = void . sequence . mapMaybe check . tails f = void . sequence . mapMaybe check . tails
@@ -320,11 +338,12 @@ checkFindExecWithSingleArgument = CommandCheck (Basename "find") (f . arguments)
commandRegex = mkRegex "[ |;]" commandRegex = mkRegex "[ |;]"
prop_checkUnusedEchoEscapes1 = verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n'" -- |
prop_checkUnusedEchoEscapes2 = verifyNot checkUnusedEchoEscapes "echo -e 'foi\\nbar'" -- >>> prop $ verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n'"
prop_checkUnusedEchoEscapes3 = verify checkUnusedEchoEscapes "echo \"n:\\t42\"" -- >>> prop $ verifyNot checkUnusedEchoEscapes "echo -e 'foi\\nbar'"
prop_checkUnusedEchoEscapes4 = verifyNot checkUnusedEchoEscapes "echo lol" -- >>> prop $ verify checkUnusedEchoEscapes "echo \"n:\\t42\""
prop_checkUnusedEchoEscapes5 = verifyNot checkUnusedEchoEscapes "echo -n -e '\n'" -- >>> prop $ verifyNot checkUnusedEchoEscapes "echo lol"
-- >>> prop $ verifyNot checkUnusedEchoEscapes "echo -n -e '\n'"
checkUnusedEchoEscapes = CommandCheck (Basename "echo") f checkUnusedEchoEscapes = CommandCheck (Basename "echo") f
where where
hasEscapes = mkRegex "\\\\[rnt]" hasEscapes = mkRegex "\\\\[rnt]"
@@ -339,9 +358,10 @@ checkUnusedEchoEscapes = CommandCheck (Basename "echo") f
info (getId token) 2028 "echo may not expand escape sequences. Use printf." 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 $ verify checkInjectableFindSh "find . -exec sh -c 'echo {}' \\;"
prop_checkInjectableFindSh3 = verifyNot checkInjectableFindSh "find . -exec sh -c 'rm \"$@\"' _ {} \\;" -- >>> prop $ verify checkInjectableFindSh "find . -execdir bash -c 'rm \"{}\"' ';'"
-- >>> prop $ verifyNot checkInjectableFindSh "find . -exec sh -c 'rm \"$@\"' _ {} \\;"
checkInjectableFindSh = CommandCheck (Basename "find") (check . arguments) checkInjectableFindSh = CommandCheck (Basename "find") (check . arguments)
where where
check args = do check args = do
@@ -364,9 +384,10 @@ checkInjectableFindSh = CommandCheck (Basename "find") (check . arguments)
warn id 2156 "Injecting filenames is fragile and insecure. Use parameters." 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 $ verify checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au' -exec rm {} +"
prop_checkFindActionPrecedence3 = verifyNot checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au'" -- >>> 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) checkFindActionPrecedence = CommandCheck (Basename "find") (f . arguments)
where where
pattern = [isMatch, const True, isParam ["-o", "-or"], isMatch, const True, isAction] pattern = [isMatch, const True, isParam ["-o", "-or"], isMatch, const True, isAction]
@@ -383,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." 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 $ verify checkMkdirDashPM "mkdir -p -m 0755 a/b"
prop_checkMkdirDashPM2 = verify checkMkdirDashPM "mkdir -vpm 0755 a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir -pm 0755 $dir"
prop_checkMkdirDashPM3 = verify checkMkdirDashPM "mkdir -pm 0755 -v a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir -vpm 0755 a/b"
prop_checkMkdirDashPM4 = verify checkMkdirDashPM "mkdir --parents --mode=0755 a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir -pm 0755 -v a/b"
prop_checkMkdirDashPM5 = verify checkMkdirDashPM "mkdir --parents --mode 0755 a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir --parents --mode=0755 a/b"
prop_checkMkdirDashPM6 = verify checkMkdirDashPM "mkdir -p --mode=0755 a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir --parents --mode 0755 a/b"
prop_checkMkdirDashPM7 = verify checkMkdirDashPM "mkdir --parents -m 0755 a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir -p --mode=0755 a/b"
prop_checkMkdirDashPM8 = verifyNot checkMkdirDashPM "mkdir -p a/b" -- >>> prop $ verify checkMkdirDashPM "mkdir --parents -m 0755 a/b"
prop_checkMkdirDashPM9 = verifyNot checkMkdirDashPM "mkdir -m 0755 a/b" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p a/b"
prop_checkMkdirDashPM10 = verifyNot checkMkdirDashPM "mkdir a/b" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir -m 0755 a/b"
prop_checkMkdirDashPM11 = verifyNot checkMkdirDashPM "mkdir --parents a/b" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir a/b"
prop_checkMkdirDashPM12 = verifyNot checkMkdirDashPM "mkdir --mode=0755 a/b" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir --parents a/b"
prop_checkMkdirDashPM13 = verifyNot checkMkdirDashPM "mkdir_func -pm 0755 a/b" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir --mode=0755 a/b"
prop_checkMkdirDashPM14 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 singlelevel" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir_func -pm 0755 a/b"
prop_checkMkdirDashPM15 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../bin" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 singlelevel"
prop_checkMkdirDashPM16 = verify checkMkdirDashPM "mkdir -p -m 0755 ../bin/laden" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ../bin"
prop_checkMkdirDashPM17 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./bin" -- >>> prop $ verify checkMkdirDashPM "mkdir -p -m 0755 ../bin/laden"
prop_checkMkdirDashPM18 = verify checkMkdirDashPM "mkdir -p -m 0755 ./bin/laden" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./bin"
prop_checkMkdirDashPM19 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./../bin" -- >>> prop $ verify checkMkdirDashPM "mkdir -p -m 0755 ./bin/laden"
prop_checkMkdirDashPM20 = verifyNot checkMkdirDashPM "mkdir -p -m 0755 .././bin" -- >>> prop $ verifyNot checkMkdirDashPM "mkdir -p -m 0755 ./../bin"
prop_checkMkdirDashPM21 = 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 checkMkdirDashPM = CommandCheck (Basename "mkdir") check
where where
check t = potentially $ do check t = potentially $ do
@@ -420,13 +442,14 @@ checkMkdirDashPM = CommandCheck (Basename "mkdir") check
re = mkRegex "^(\\.\\.?\\/)+[^/]+$" re = mkRegex "^(\\.\\.?\\/)+[^/]+$"
prop_checkNonportableSignals1 = verify checkNonportableSignals "trap f 8" -- |
prop_checkNonportableSignals2 = verifyNot checkNonportableSignals "trap f 0" -- >>> prop $ verify checkNonportableSignals "trap f 8"
prop_checkNonportableSignals3 = verifyNot checkNonportableSignals "trap f 14" -- >>> prop $ verifyNot checkNonportableSignals "trap f 0"
prop_checkNonportableSignals4 = verify checkNonportableSignals "trap f SIGKILL" -- >>> prop $ verifyNot checkNonportableSignals "trap f 14"
prop_checkNonportableSignals5 = verify checkNonportableSignals "trap f 9" -- >>> prop $ verify checkNonportableSignals "trap f SIGKILL"
prop_checkNonportableSignals6 = verify checkNonportableSignals "trap f stop" -- >>> prop $ verify checkNonportableSignals "trap f 9"
prop_checkNonportableSignals7 = verifyNot checkNonportableSignals "trap 'stop' int" -- >>> prop $ verify checkNonportableSignals "trap f stop"
-- >>> prop $ verifyNot checkNonportableSignals "trap 'stop' int"
checkNonportableSignals = CommandCheck (Exactly "trap") (f . arguments) checkNonportableSignals = CommandCheck (Exactly "trap") (f . arguments)
where where
f args = case args of f args = case args of
@@ -455,10 +478,11 @@ checkNonportableSignals = CommandCheck (Exactly "trap") (f . arguments)
"SIGKILL/SIGSTOP can not be trapped." "SIGKILL/SIGSTOP can not be trapped."
prop_checkInteractiveSu1 = verify checkInteractiveSu "su; rm file; su $USER" -- |
prop_checkInteractiveSu2 = verify checkInteractiveSu "su foo; something; exit" -- >>> prop $ verify checkInteractiveSu "su; rm file; su $USER"
prop_checkInteractiveSu3 = verifyNot checkInteractiveSu "echo rm | su foo" -- >>> prop $ verify checkInteractiveSu "su foo; something; exit"
prop_checkInteractiveSu4 = verifyNot checkInteractiveSu "su root < script" -- >>> prop $ verifyNot checkInteractiveSu "echo rm | su foo"
-- >>> prop $ verifyNot checkInteractiveSu "su root < script"
checkInteractiveSu = CommandCheck (Basename "su") f checkInteractiveSu = CommandCheck (Basename "su") f
where where
f cmd = when (length (arguments cmd) <= 1) $ do f cmd = when (length (arguments cmd) <= 1) $ do
@@ -473,11 +497,13 @@ checkInteractiveSu = CommandCheck (Basename "su") f
undirected _ = True undirected _ = True
-- |
-- This is hard to get right without properly parsing ssh args -- 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 $ verify checkSshCommandString "ssh host \"echo $PS1\""
prop_checkSshCmdStr3 = verifyNot checkSshCommandString "ssh \"$host\"" -- >>> prop $ verifyNot checkSshCommandString "ssh host \"ls foo\""
prop_checkSshCmdStr4 = verifyNot checkSshCommandString "ssh -i key \"$host\"" -- >>> prop $ verifyNot checkSshCommandString "ssh \"$host\""
-- >>> prop $ verifyNot checkSshCommandString "ssh -i key \"$host\""
checkSshCommandString = CommandCheck (Basename "ssh") (f . arguments) checkSshCommandString = CommandCheck (Basename "ssh") (f . arguments)
where where
isOption x = "-" `isPrefixOf` (concat $ oversimplify x) isOption x = "-" `isPrefixOf` (concat $ oversimplify x)
@@ -493,24 +519,25 @@ checkSshCommandString = CommandCheck (Basename "ssh") (f . arguments)
checkArg _ = return () checkArg _ = return ()
prop_checkPrintfVar1 = verify checkPrintfVar "printf \"Lol: $s\"" -- |
prop_checkPrintfVar2 = verifyNot checkPrintfVar "printf 'Lol: $s'" -- >>> prop $ verify checkPrintfVar "printf \"Lol: $s\""
prop_checkPrintfVar3 = verify checkPrintfVar "printf -v cow $(cmd)" -- >>> prop $ verifyNot checkPrintfVar "printf 'Lol: $s'"
prop_checkPrintfVar4 = verifyNot checkPrintfVar "printf \"%${count}s\" var" -- >>> prop $ verify checkPrintfVar "printf -v cow $(cmd)"
prop_checkPrintfVar5 = verify checkPrintfVar "printf '%s %s %s' foo bar" -- >>> prop $ verifyNot checkPrintfVar "printf \"%${count}s\" var"
prop_checkPrintfVar6 = verify checkPrintfVar "printf foo bar baz" -- >>> prop $ verify checkPrintfVar "printf '%s %s %s' foo bar"
prop_checkPrintfVar7 = verify checkPrintfVar "printf -- foo bar baz" -- >>> prop $ verify checkPrintfVar "printf foo bar baz"
prop_checkPrintfVar8 = verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\"" -- >>> prop $ verify checkPrintfVar "printf -- foo bar baz"
prop_checkPrintfVar9 = verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png" -- >>> prop $ verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\""
prop_checkPrintfVar10= verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz" -- >>> prop $ verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png"
prop_checkPrintfVar11= verifyNot checkPrintfVar "printf '%(%s%s)T' -1" -- >>> prop $ verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz"
prop_checkPrintfVar12= verify checkPrintfVar "printf '%s %s\\n' 1 2 3" -- >>> prop $ verifyNot checkPrintfVar "printf '%(%s%s)T' -1"
prop_checkPrintfVar13= verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4" -- >>> prop $ verify checkPrintfVar "printf '%s %s\\n' 1 2 3"
prop_checkPrintfVar14= verify checkPrintfVar "printf '%*s\\n' 1" -- >>> prop $ verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4"
prop_checkPrintfVar15= verifyNot checkPrintfVar "printf '%*s\\n' 1 2" -- >>> prop $ verify checkPrintfVar "printf '%*s\\n' 1"
prop_checkPrintfVar16= verifyNot checkPrintfVar "printf $'string'" -- >>> prop $ verifyNot checkPrintfVar "printf '%*s\\n' 1 2"
prop_checkPrintfVar17= verify checkPrintfVar "printf '%-*s\\n' 1" -- >>> prop $ verifyNot checkPrintfVar "printf $'string'"
prop_checkPrintfVar18= verifyNot checkPrintfVar "printf '%-*s\\n' 1 2" -- >>> prop $ verify checkPrintfVar "printf '%-*s\\n' 1"
-- >>> prop $ verifyNot checkPrintfVar "printf '%-*s\\n' 1 2"
checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where
f (doubledash:rest) | getLiteralString doubledash == Just "--" = f rest f (doubledash:rest) | getLiteralString doubledash == Just "--" = f rest
f (dashv:var:rest) | getLiteralString dashv == Just "-v" = f rest f (dashv:var:rest) | getLiteralString dashv == Just "-v" = f rest
@@ -559,24 +586,26 @@ checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where
prop_checkUuoeCmd1 = verify checkUuoeCmd "echo $(date)" -- |
prop_checkUuoeCmd2 = verify checkUuoeCmd "echo `date`" -- >>> prop $ verify checkUuoeCmd "echo $(date)"
prop_checkUuoeCmd3 = verify checkUuoeCmd "echo \"$(date)\"" -- >>> prop $ verify checkUuoeCmd "echo `date`"
prop_checkUuoeCmd4 = verify checkUuoeCmd "echo \"`date`\"" -- >>> prop $ verify checkUuoeCmd "echo \"$(date)\""
prop_checkUuoeCmd5 = verifyNot checkUuoeCmd "echo \"The time is $(date)\"" -- >>> prop $ verify checkUuoeCmd "echo \"`date`\""
prop_checkUuoeCmd6 = verifyNot checkUuoeCmd "echo \"$(<file)\"" -- >>> prop $ verifyNot checkUuoeCmd "echo \"The time is $(date)\""
-- >>> prop $ verifyNot checkUuoeCmd "echo \"$(<file)\""
checkUuoeCmd = CommandCheck (Exactly "echo") (f . arguments) where checkUuoeCmd = CommandCheck (Exactly "echo") (f . arguments) where
msg id = style id 2005 "Useless echo? Instead of 'echo $(cmd)', just use 'cmd'." msg id = style id 2005 "Useless echo? Instead of 'echo $(cmd)', just use 'cmd'."
f [token] = when (tokenIsJustCommandOutput token) $ msg (getId token) f [token] = when (tokenIsJustCommandOutput token) $ msg (getId token)
f _ = return () f _ = return ()
prop_checkSetAssignment1 = verify checkSetAssignment "set foo 42" -- |
prop_checkSetAssignment2 = verify checkSetAssignment "set foo = 42" -- >>> prop $ verify checkSetAssignment "set foo 42"
prop_checkSetAssignment3 = verify checkSetAssignment "set foo=42" -- >>> prop $ verify checkSetAssignment "set foo = 42"
prop_checkSetAssignment4 = verifyNot checkSetAssignment "set -- if=/dev/null" -- >>> prop $ verify checkSetAssignment "set foo=42"
prop_checkSetAssignment5 = verifyNot checkSetAssignment "set 'a=5'" -- >>> prop $ verifyNot checkSetAssignment "set -- if=/dev/null"
prop_checkSetAssignment6 = verifyNot checkSetAssignment "set" -- >>> prop $ verifyNot checkSetAssignment "set 'a=5'"
-- >>> prop $ verifyNot checkSetAssignment "set"
checkSetAssignment = CommandCheck (Exactly "set") (f . arguments) checkSetAssignment = CommandCheck (Exactly "set") (f . arguments)
where where
f (var:value:rest) = f (var:value:rest) =
@@ -596,10 +625,11 @@ checkSetAssignment = CommandCheck (Exactly "set") (f . arguments)
literal _ = "*" literal _ = "*"
prop_checkExportedExpansions1 = verify checkExportedExpansions "export $foo" -- |
prop_checkExportedExpansions2 = verify checkExportedExpansions "export \"$foo\"" -- >>> prop $ verify checkExportedExpansions "export $foo"
prop_checkExportedExpansions3 = verifyNot checkExportedExpansions "export foo" -- >>> prop $ verify checkExportedExpansions "export \"$foo\""
prop_checkExportedExpansions4 = verifyNot checkExportedExpansions "export ${foo?}" -- >>> prop $ verifyNot checkExportedExpansions "export foo"
-- >>> prop $ verifyNot checkExportedExpansions "export ${foo?}"
checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . arguments) checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . arguments)
where where
check t = potentially $ do check t = potentially $ do
@@ -608,14 +638,15 @@ checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . argumen
return . warn (getId t) 2163 $ return . warn (getId t) 2163 $
"This does not export '" ++ name ++ "'. Remove $/${} for that, or use ${var?} to quiet." "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 $ verify checkReadExpansions "read $var"
prop_checkReadExpansions3 = verifyNot checkReadExpansions "read -p $var" -- >>> prop $ verify checkReadExpansions "read -r $var"
prop_checkReadExpansions4 = verifyNot checkReadExpansions "read -rd $delim name" -- >>> prop $ verifyNot checkReadExpansions "read -p $var"
prop_checkReadExpansions5 = verify checkReadExpansions "read \"$var\"" -- >>> prop $ verifyNot checkReadExpansions "read -rd $delim name"
prop_checkReadExpansions6 = verify checkReadExpansions "read -a $var" -- >>> prop $ verify checkReadExpansions "read \"$var\""
prop_checkReadExpansions7 = verifyNot checkReadExpansions "read $1" -- >>> prop $ verify checkReadExpansions "read -a $var"
prop_checkReadExpansions8 = verifyNot checkReadExpansions "read ${var?}" -- >>> prop $ verifyNot checkReadExpansions "read $1"
-- >>> prop $ verifyNot checkReadExpansions "read ${var?}"
checkReadExpansions = CommandCheck (Exactly "read") check checkReadExpansions = CommandCheck (Exactly "read") check
where where
options = getGnuOpts "sreu:n:N:i:p:a:" options = getGnuOpts "sreu:n:N:i:p:a:"
@@ -642,9 +673,10 @@ getSingleUnmodifiedVariable word =
in guard (contents == name) >> return t in guard (contents == name) >> return t
_ -> Nothing _ -> Nothing
prop_checkAliasesUsesArgs1 = verify checkAliasesUsesArgs "alias a='cp $1 /a'" -- |
prop_checkAliasesUsesArgs2 = verifyNot checkAliasesUsesArgs "alias $1='foo'" -- >>> prop $ verify checkAliasesUsesArgs "alias a='cp $1 /a'"
prop_checkAliasesUsesArgs3 = verify checkAliasesUsesArgs "alias a=\"echo \\${@}\"" -- >>> prop $ verifyNot checkAliasesUsesArgs "alias $1='foo'"
-- >>> prop $ verify checkAliasesUsesArgs "alias a=\"echo \\${@}\""
checkAliasesUsesArgs = CommandCheck (Exactly "alias") (f . arguments) checkAliasesUsesArgs = CommandCheck (Exactly "alias") (f . arguments)
where where
re = mkRegex "\\$\\{?[0-9*@]" re = mkRegex "\\$\\{?[0-9*@]"
@@ -656,9 +688,10 @@ checkAliasesUsesArgs = CommandCheck (Exactly "alias") (f . arguments)
"Aliases can't use positional parameters. Use a function." "Aliases can't use positional parameters. Use a function."
prop_checkAliasesExpandEarly1 = verify checkAliasesExpandEarly "alias foo=\"echo $PWD\"" -- |
prop_checkAliasesExpandEarly2 = verifyNot checkAliasesExpandEarly "alias -p" -- >>> prop $ verify checkAliasesExpandEarly "alias foo=\"echo $PWD\""
prop_checkAliasesExpandEarly3 = verifyNot checkAliasesExpandEarly "alias foo='echo {1..10}'" -- >>> prop $ verifyNot checkAliasesExpandEarly "alias -p"
-- >>> prop $ verifyNot checkAliasesExpandEarly "alias foo='echo {1..10}'"
checkAliasesExpandEarly = CommandCheck (Exactly "alias") (f . arguments) checkAliasesExpandEarly = CommandCheck (Exactly "alias") (f . arguments)
where where
f = mapM_ checkArg f = mapM_ checkArg
@@ -668,8 +701,8 @@ checkAliasesExpandEarly = CommandCheck (Exactly "alias") (f . arguments)
checkArg _ = return () checkArg _ = return ()
prop_checkUnsetGlobs1 = verify checkUnsetGlobs "unset foo[1]" -- >>> prop $ verify checkUnsetGlobs "unset foo[1]"
prop_checkUnsetGlobs2 = verifyNot checkUnsetGlobs "unset foo" -- >>> prop $ verifyNot checkUnsetGlobs "unset foo"
checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments) checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments)
where where
check arg = check arg =
@@ -677,14 +710,15 @@ checkUnsetGlobs = CommandCheck (Exactly "unset") (mapM_ check . arguments)
warn (getId arg) 2184 "Quote arguments to unset so they're not glob expanded." 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 $ verify checkFindWithoutPath "find -type f"
prop_checkFindWithoutPath3 = verifyNot checkFindWithoutPath "find . -type f" -- >>> prop $ verify checkFindWithoutPath "find"
prop_checkFindWithoutPath4 = verifyNot checkFindWithoutPath "find -H -L \"$path\" -print" -- >>> prop $ verifyNot checkFindWithoutPath "find . -type f"
prop_checkFindWithoutPath5 = verifyNot checkFindWithoutPath "find -O3 ." -- >>> prop $ verifyNot checkFindWithoutPath "find -H -L \"$path\" -print"
prop_checkFindWithoutPath6 = verifyNot checkFindWithoutPath "find -D exec ." -- >>> prop $ verifyNot checkFindWithoutPath "find -O3 ."
prop_checkFindWithoutPath7 = verifyNot checkFindWithoutPath "find --help" -- >>> prop $ verifyNot checkFindWithoutPath "find -D exec ."
prop_checkFindWithoutPath8 = verifyNot checkFindWithoutPath "find -Hx . -print" -- >>> prop $ verifyNot checkFindWithoutPath "find --help"
-- >>> prop $ verifyNot checkFindWithoutPath "find -Hx . -print"
checkFindWithoutPath = CommandCheck (Basename "find") f checkFindWithoutPath = CommandCheck (Basename "find") f
where where
f t@(T_SimpleCommand _ _ (cmd:args)) = f t@(T_SimpleCommand _ _ (cmd:args)) =
@@ -703,10 +737,11 @@ checkFindWithoutPath = CommandCheck (Basename "find") f
leadingFlagChars="-EHLPXdfsxO0123456789" leadingFlagChars="-EHLPXdfsxO0123456789"
prop_checkTimeParameters1 = verify checkTimeParameters "time -f lol sleep 10" -- |
prop_checkTimeParameters2 = verifyNot checkTimeParameters "time sleep 10" -- >>> prop $ verify checkTimeParameters "time -f lol sleep 10"
prop_checkTimeParameters3 = verifyNot checkTimeParameters "time -p foo" -- >>> prop $ verifyNot checkTimeParameters "time sleep 10"
prop_checkTimeParameters4 = verifyNot checkTimeParameters "command time -f lol sleep 10" -- >>> prop $ verifyNot checkTimeParameters "time -p foo"
-- >>> prop $ verifyNot checkTimeParameters "command time -f lol sleep 10"
checkTimeParameters = CommandCheck (Exactly "time") f checkTimeParameters = CommandCheck (Exactly "time") f
where where
f (T_SimpleCommand _ _ (cmd:args:_)) = f (T_SimpleCommand _ _ (cmd:args:_)) =
@@ -717,9 +752,10 @@ checkTimeParameters = CommandCheck (Exactly "time") f
f _ = return () f _ = return ()
prop_checkTimedCommand1 = verify checkTimedCommand "#!/bin/sh\ntime -p foo | bar" -- |
prop_checkTimedCommand2 = verify checkTimedCommand "#!/bin/dash\ntime ( foo; bar; )" -- >>> prop $ verify checkTimedCommand "#!/bin/sh\ntime -p foo | bar"
prop_checkTimedCommand3 = verifyNot checkTimedCommand "#!/bin/sh\ntime sleep 1" -- >>> prop $ verify checkTimedCommand "#!/bin/dash\ntime ( foo; bar; )"
-- >>> prop $ verifyNot checkTimedCommand "#!/bin/sh\ntime sleep 1"
checkTimedCommand = CommandCheck (Exactly "time") f where checkTimedCommand = CommandCheck (Exactly "time") f where
f (T_SimpleCommand _ _ (c:args@(_:_))) = f (T_SimpleCommand _ _ (c:args@(_:_))) =
whenShell [Sh, Dash] $ do whenShell [Sh, Dash] $ do
@@ -743,32 +779,37 @@ checkTimedCommand = CommandCheck (Exactly "time") f where
T_SimpleCommand {} -> return True T_SimpleCommand {} -> return True
_ -> return False _ -> 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 -> checkLocalScope = CommandCheck (Exactly "local") $ \t ->
whenShell [Bash, Dash] $ do -- Ksh allows it, Sh doesn't support local whenShell [Bash, Dash] $ do -- Ksh allows it, Sh doesn't support local
path <- getPathM t path <- getPathM t
unless (any isFunction path) $ unless (any isFunction path) $
err (getId $ getCommandTokenOrThis t) 2168 "'local' is only valid in functions." 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") $ checkDeprecatedTempfile = CommandCheck (Basename "tempfile") $
\t -> warn (getId $ getCommandTokenOrThis t) 2186 "tempfile is deprecated. Use mktemp instead." \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") $ checkDeprecatedEgrep = CommandCheck (Basename "egrep") $
\t -> info (getId $ getCommandTokenOrThis t) 2196 "egrep is non-standard and deprecated. Use grep -E instead." \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") $ checkDeprecatedFgrep = CommandCheck (Basename "fgrep") $
\t -> info (getId $ getCommandTokenOrThis t) 2197 "fgrep is non-standard and deprecated. Use grep -F instead." \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 $ verify checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; esac; done"
prop_checkWhileGetoptsCase3 = verifyNot checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done" -- >>> prop $ verify checkWhileGetoptsCase "while getopts 'a:' 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 $ verifyNot checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done"
prop_checkWhileGetoptsCase5 = verifyNot checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; 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 checkWhileGetoptsCase = CommandCheck (Exactly "getopts") f
where where
f :: Token -> Analysis f :: Token -> Analysis
@@ -833,19 +874,20 @@ checkWhileGetoptsCase = CommandCheck (Exactly "getopts") f
T_Redirecting _ _ x@(T_CaseExpression {}) -> return x T_Redirecting _ _ x@(T_CaseExpression {}) -> return x
_ -> Nothing _ -> Nothing
prop_checkCatastrophicRm1 = verify checkCatastrophicRm "rm -r $1/$2" -- |
prop_checkCatastrophicRm2 = verify checkCatastrophicRm "rm -r /home/$foo" -- >>> prop $ verify checkCatastrophicRm "rm -r $1/$2"
prop_checkCatastrophicRm3 = verifyNot checkCatastrophicRm "rm -r /home/${USER:?}/*" -- >>> prop $ verify checkCatastrophicRm "rm -r /home/$foo"
prop_checkCatastrophicRm4 = verify checkCatastrophicRm "rm -fr /home/$(whoami)/*" -- >>> prop $ verifyNot checkCatastrophicRm "rm -r /home/${USER:?}/*"
prop_checkCatastrophicRm5 = verifyNot checkCatastrophicRm "rm -r /home/${USER:-thing}/*" -- >>> prop $ verify checkCatastrophicRm "rm -fr /home/$(whoami)/*"
prop_checkCatastrophicRm6 = verify checkCatastrophicRm "rm --recursive /etc/*$config*" -- >>> prop $ verifyNot checkCatastrophicRm "rm -r /home/${USER:-thing}/*"
prop_checkCatastrophicRm8 = verify checkCatastrophicRm "rm -rf /home" -- >>> prop $ verify checkCatastrophicRm "rm --recursive /etc/*$config*"
prop_checkCatastrophicRm10= verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}" -- >>> prop $ verify checkCatastrophicRm "rm -rf /home"
prop_checkCatastrophicRm11= verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec" -- >>> prop $ verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}"
prop_checkCatastrophicRm12= verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbin}}/$exec" -- >>> prop $ verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec"
prop_checkCatastrophicRm13= verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}/$exec" -- >>> prop $ verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbin}}/$exec"
prop_checkCatastrophicRmA = verify checkCatastrophicRm "rm -rf /usr /lib/nvidia-current/xorg/xorg" -- >>> prop $ verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}/$exec"
prop_checkCatastrophicRmB = verify checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*" -- >>> prop $ verify checkCatastrophicRm "rm -rf /usr /lib/nvidia-current/xorg/xorg"
-- >>> prop $ verify checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*"
checkCatastrophicRm = CommandCheck (Basename "rm") $ \t -> checkCatastrophicRm = CommandCheck (Basename "rm") $ \t ->
when (isRecursive t) $ when (isRecursive t) $
mapM_ (mapM_ checkWord . braceExpand) $ arguments t mapM_ (mapM_ checkWord . braceExpand) $ arguments t
@@ -894,8 +936,9 @@ checkCatastrophicRm = CommandCheck (Basename "rm") $ \t ->
["", "/", "/*", "/*/*"] >>= (\x -> map (++x) paths) ["", "/", "/*", "/*/*"] >>= (\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 checkLetUsage = CommandCheck (Exactly "let") f
where where
f t = whenShell [Bash,Ksh] $ do f t = whenShell [Bash,Ksh] $ do
@@ -915,15 +958,16 @@ missingDestination handler token = do
any (\x -> x /= "" && x `isPrefixOf` "target-directory") $ any (\x -> x /= "" && x `isPrefixOf` "target-directory") $
map snd args map snd args
prop_checkMvArguments1 = verify checkMvArguments "mv 'foo bar'" -- |
prop_checkMvArguments2 = verifyNot checkMvArguments "mv foo bar" -- >>> prop $ verify checkMvArguments "mv 'foo bar'"
prop_checkMvArguments3 = verifyNot checkMvArguments "mv 'foo bar'{,bak}" -- >>> prop $ verifyNot checkMvArguments "mv foo bar"
prop_checkMvArguments4 = verifyNot checkMvArguments "mv \"$@\"" -- >>> prop $ verifyNot checkMvArguments "mv 'foo bar'{,bak}"
prop_checkMvArguments5 = verifyNot checkMvArguments "mv -t foo bar" -- >>> prop $ verifyNot checkMvArguments "mv \"$@\""
prop_checkMvArguments6 = verifyNot checkMvArguments "mv --target-directory=foo bar" -- >>> prop $ verifyNot checkMvArguments "mv -t foo bar"
prop_checkMvArguments7 = verifyNot checkMvArguments "mv --target-direc=foo bar" -- >>> prop $ verifyNot checkMvArguments "mv --target-directory=foo bar"
prop_checkMvArguments8 = verifyNot checkMvArguments "mv --version" -- >>> prop $ verifyNot checkMvArguments "mv --target-direc=foo bar"
prop_checkMvArguments9 = verifyNot checkMvArguments "mv \"${!var}\"" -- >>> prop $ verifyNot checkMvArguments "mv --version"
-- >>> prop $ verifyNot checkMvArguments "mv \"${!var}\""
checkMvArguments = CommandCheck (Basename "mv") $ missingDestination f checkMvArguments = CommandCheck (Basename "mv") $ missingDestination f
where where
f t = err (getId t) 2224 "This mv has no destination. Check the arguments." f t = err (getId t) 2224 "This mv has no destination. Check the arguments."
@@ -937,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." 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 $ verify checkFindRedirections "find . -exec echo {} > file \\;"
prop_checkFindRedirections3 = verifyNot checkFindRedirections "find . -execdir sh -c 'foo > file' \\;" -- >>> prop $ verifyNot checkFindRedirections "find . -exec echo {} \\; > file"
-- >>> prop $ verifyNot checkFindRedirections "find . -execdir sh -c 'foo > file' \\;"
checkFindRedirections = CommandCheck (Basename "find") f checkFindRedirections = CommandCheck (Basename "find") f
where where
f t = do f t = do
@@ -954,17 +999,18 @@ checkFindRedirections = CommandCheck (Basename "find") f
"Redirection applies to the find command itself. Rewrite to work per action (or move to end)." "Redirection applies to the find command itself. Rewrite to work per action (or move to end)."
_ -> return () _ -> return ()
prop_checkWhich = verify checkWhich "which '.+'" -- >>> prop $ verify checkWhich "which '.+'"
checkWhich = CommandCheck (Basename "which") $ checkWhich = CommandCheck (Basename "which") $
\t -> info (getId $ getCommandTokenOrThis t) 2230 "which is non-standard. Use builtin 'command -v' instead." \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 $ verify checkSudoRedirect "sudo echo 3 > /proc/file"
prop_checkSudoRedirect3 = verify checkSudoRedirect "sudo cmd >> file" -- >>> prop $ verify checkSudoRedirect "sudo cmd < input"
prop_checkSudoRedirect4 = verify checkSudoRedirect "sudo cmd &> file" -- >>> prop $ verify checkSudoRedirect "sudo cmd >> file"
prop_checkSudoRedirect5 = verifyNot checkSudoRedirect "sudo cmd 2>&1" -- >>> prop $ verify checkSudoRedirect "sudo cmd &> file"
prop_checkSudoRedirect6 = verifyNot checkSudoRedirect "sudo cmd 2> log" -- >>> prop $ verifyNot checkSudoRedirect "sudo cmd 2>&1"
prop_checkSudoRedirect7 = verifyNot checkSudoRedirect "sudo cmd > /dev/null 2>&1" -- >>> prop $ verifyNot checkSudoRedirect "sudo cmd 2> log"
-- >>> prop $ verifyNot checkSudoRedirect "sudo cmd > /dev/null 2>&1"
checkSudoRedirect = CommandCheck (Basename "sudo") f checkSudoRedirect = CommandCheck (Basename "sudo") f
where where
f t = do f t = do
@@ -988,13 +1034,14 @@ checkSudoRedirect = CommandCheck (Basename "sudo") f
warnAbout _ = return () warnAbout _ = return ()
special file = concat (oversimplify file) == "/dev/null" special file = concat (oversimplify file) == "/dev/null"
prop_checkSudoArgs1 = verify checkSudoArgs "sudo cd /root" -- |
prop_checkSudoArgs2 = verify checkSudoArgs "sudo export x=3" -- >>> prop $ verify checkSudoArgs "sudo cd /root"
prop_checkSudoArgs3 = verifyNot checkSudoArgs "sudo ls /usr/local/protected" -- >>> prop $ verify checkSudoArgs "sudo export x=3"
prop_checkSudoArgs4 = verifyNot checkSudoArgs "sudo ls && export x=3" -- >>> prop $ verifyNot checkSudoArgs "sudo ls /usr/local/protected"
prop_checkSudoArgs5 = verifyNot checkSudoArgs "sudo echo ls" -- >>> prop $ verifyNot checkSudoArgs "sudo ls && export x=3"
prop_checkSudoArgs6 = verifyNot checkSudoArgs "sudo -n -u export ls" -- >>> prop $ verifyNot checkSudoArgs "sudo echo ls"
prop_checkSudoArgs7 = verifyNot checkSudoArgs "sudo docker export foo" -- >>> prop $ verifyNot checkSudoArgs "sudo -n -u export ls"
-- >>> prop $ verifyNot checkSudoArgs "sudo docker export foo"
checkSudoArgs = CommandCheck (Basename "sudo") f checkSudoArgs = CommandCheck (Basename "sudo") f
where where
f t = potentially $ do f t = potentially $ do
@@ -1008,5 +1055,14 @@ checkSudoArgs = CommandCheck (Basename "sudo") f
-- This mess is why ShellCheck prefers not to know. -- This mess is why ShellCheck prefers not to know.
parseOpts = getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:" parseOpts = getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:"
return [] -- |
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |]) -- >>> 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] $
case arguments t of
(file:arg1:_) -> warn (getId arg1) 2240 $
"The dot command does not support arguments in sh/dash. Set them as variables."
_ -> return ()

View File

@@ -17,9 +17,8 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
module ShellCheck.Checks.ShellSupport (checker , ShellCheck.Checks.ShellSupport.runTests) where module ShellCheck.Checks.ShellSupport (checker) where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
@@ -33,8 +32,6 @@ import Data.Char
import Data.List import Data.List
import Data.Maybe import Data.Maybe
import qualified Data.Map as Map import qualified Data.Map as Map
import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)
data ForShell = ForShell [Shell] (Token -> Analysis) data ForShell = ForShell [Shell] (Token -> Analysis)
@@ -67,9 +64,10 @@ testChecker (ForShell _ t) =
verify c s = producesComments (testChecker c) s == Just True verify c s = producesComments (testChecker c) s == Just True
verifyNot c s = producesComments (testChecker c) s == Just False 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 $ verify checkForDecimals "((3.14*c))"
prop_checkForDecimals3 = verifyNot checkForDecimals "declare -A foo; foo[1.2]=bar" -- >>> prop $ verify checkForDecimals "foo[1.2]=bar"
-- >>> prop $ verifyNot checkForDecimals "declare -A foo; foo[1.2]=bar"
checkForDecimals = ForShell [Sh, Dash, Bash] f checkForDecimals = ForShell [Sh, Dash, Bash] f
where where
f t@(TA_Expansion id _) = potentially $ do f t@(TA_Expansion id _) = potentially $ do
@@ -80,62 +78,63 @@ checkForDecimals = ForShell [Sh, Dash, Bash] f
f _ = return () f _ = return ()
prop_checkBashisms = verify checkBashisms "while read a; do :; done < <(a)" -- |
prop_checkBashisms2 = verify checkBashisms "[ foo -nt bar ]" -- >>> prop $ verify checkBashisms "while read a; do :; done < <(a)"
prop_checkBashisms3 = verify checkBashisms "echo $((i++))" -- >>> prop $ verify checkBashisms "[ foo -nt bar ]"
prop_checkBashisms4 = verify checkBashisms "rm !(*.hs)" -- >>> prop $ verify checkBashisms "echo $((i++))"
prop_checkBashisms5 = verify checkBashisms "source file" -- >>> prop $ verify checkBashisms "rm !(*.hs)"
prop_checkBashisms6 = verify checkBashisms "[ \"$a\" == 42 ]" -- >>> prop $ verify checkBashisms "source file"
prop_checkBashisms7 = verify checkBashisms "echo ${var[1]}" -- >>> prop $ verify checkBashisms "[ \"$a\" == 42 ]"
prop_checkBashisms8 = verify checkBashisms "echo ${!var[@]}" -- >>> prop $ verify checkBashisms "echo ${var[1]}"
prop_checkBashisms9 = verify checkBashisms "echo ${!var*}" -- >>> prop $ verify checkBashisms "echo ${!var[@]}"
prop_checkBashisms10= verify checkBashisms "echo ${var:4:12}" -- >>> prop $ verify checkBashisms "echo ${!var*}"
prop_checkBashisms11= verifyNot checkBashisms "echo ${var:-4}" -- >>> prop $ verify checkBashisms "echo ${var:4:12}"
prop_checkBashisms12= verify checkBashisms "echo ${var//foo/bar}" -- >>> prop $ verifyNot checkBashisms "echo ${var:-4}"
prop_checkBashisms13= verify checkBashisms "exec -c env" -- >>> prop $ verify checkBashisms "echo ${var//foo/bar}"
prop_checkBashisms14= verify checkBashisms "echo -n \"Foo: \"" -- >>> prop $ verify checkBashisms "exec -c env"
prop_checkBashisms15= verify checkBashisms "let n++" -- >>> prop $ verify checkBashisms "echo -n \"Foo: \""
prop_checkBashisms16= verify checkBashisms "echo $RANDOM" -- >>> prop $ verify checkBashisms "let n++"
prop_checkBashisms17= verify checkBashisms "echo $((RANDOM%6+1))" -- >>> prop $ verify checkBashisms "echo $RANDOM"
prop_checkBashisms18= verify checkBashisms "foo &> /dev/null" -- >>> prop $ verify checkBashisms "echo $((RANDOM%6+1))"
prop_checkBashisms19= verify checkBashisms "foo > file*.txt" -- >>> prop $ verify checkBashisms "foo &> /dev/null"
prop_checkBashisms20= verify checkBashisms "read -ra foo" -- >>> prop $ verify checkBashisms "foo > file*.txt"
prop_checkBashisms21= verify checkBashisms "[ -a foo ]" -- >>> prop $ verify checkBashisms "read -ra foo"
prop_checkBashisms22= verifyNot checkBashisms "[ foo -a bar ]" -- >>> prop $ verify checkBashisms "[ -a foo ]"
prop_checkBashisms23= verify checkBashisms "trap mything ERR INT" -- >>> prop $ verifyNot checkBashisms "[ foo -a bar ]"
prop_checkBashisms24= verifyNot checkBashisms "trap mything INT TERM" -- >>> prop $ verify checkBashisms "trap mything ERR INT"
prop_checkBashisms25= verify checkBashisms "cat < /dev/tcp/host/123" -- >>> prop $ verifyNot checkBashisms "trap mything INT TERM"
prop_checkBashisms26= verify checkBashisms "trap mything ERR SIGTERM" -- >>> prop $ verify checkBashisms "cat < /dev/tcp/host/123"
prop_checkBashisms27= verify checkBashisms "echo *[^0-9]*" -- >>> prop $ verify checkBashisms "trap mything ERR SIGTERM"
prop_checkBashisms28= verify checkBashisms "exec {n}>&2" -- >>> prop $ verify checkBashisms "echo *[^0-9]*"
prop_checkBashisms29= verify checkBashisms "echo ${!var}" -- >>> prop $ verify checkBashisms "exec {n}>&2"
prop_checkBashisms30= verify checkBashisms "printf -v '%s' \"$1\"" -- >>> prop $ verify checkBashisms "echo ${!var}"
prop_checkBashisms31= verify checkBashisms "printf '%q' \"$1\"" -- >>> prop $ verify checkBashisms "printf -v '%s' \"$1\""
prop_checkBashisms32= verifyNot checkBashisms "#!/bin/dash\n[ foo -nt bar ]" -- >>> prop $ verify checkBashisms "printf '%q' \"$1\""
prop_checkBashisms33= verify checkBashisms "#!/bin/sh\necho -n foo" -- >>> prop $ verifyNot checkBashisms "#!/bin/dash\n[ foo -nt bar ]"
prop_checkBashisms34= verifyNot checkBashisms "#!/bin/dash\necho -n foo" -- >>> prop $ verify checkBashisms "#!/bin/sh\necho -n foo"
prop_checkBashisms35= verifyNot checkBashisms "#!/bin/dash\nlocal foo" -- >>> prop $ verifyNot checkBashisms "#!/bin/dash\necho -n foo"
prop_checkBashisms36= verifyNot checkBashisms "#!/bin/dash\nread -p foo -r bar" -- >>> prop $ verifyNot checkBashisms "#!/bin/dash\nlocal foo"
prop_checkBashisms37= verifyNot checkBashisms "HOSTNAME=foo; echo $HOSTNAME" -- >>> prop $ verifyNot checkBashisms "#!/bin/dash\nread -p foo -r bar"
prop_checkBashisms38= verify checkBashisms "RANDOM=9; echo $RANDOM" -- >>> prop $ verifyNot checkBashisms "HOSTNAME=foo; echo $HOSTNAME"
prop_checkBashisms39= verify checkBashisms "foo-bar() { true; }" -- >>> prop $ verify checkBashisms "RANDOM=9; echo $RANDOM"
prop_checkBashisms40= verify checkBashisms "echo $(<file)" -- >>> prop $ verify checkBashisms "foo-bar() { true; }"
prop_checkBashisms41= verify checkBashisms "echo `<file`" -- >>> prop $ verify checkBashisms "echo $(<file)"
prop_checkBashisms42= verify checkBashisms "trap foo int" -- >>> prop $ verify checkBashisms "echo `<file`"
prop_checkBashisms43= verify checkBashisms "trap foo sigint" -- >>> prop $ verify checkBashisms "trap foo int"
prop_checkBashisms44= verifyNot checkBashisms "#!/bin/dash\ntrap foo int" -- >>> prop $ verify checkBashisms "trap foo sigint"
prop_checkBashisms45= verifyNot checkBashisms "#!/bin/dash\ntrap foo INT" -- >>> prop $ verifyNot checkBashisms "#!/bin/dash\ntrap foo int"
prop_checkBashisms46= verify checkBashisms "#!/bin/dash\ntrap foo SIGINT" -- >>> prop $ verifyNot checkBashisms "#!/bin/dash\ntrap foo INT"
prop_checkBashisms47= verify checkBashisms "#!/bin/dash\necho foo 42>/dev/null" -- >>> prop $ verify checkBashisms "#!/bin/dash\ntrap foo SIGINT"
prop_checkBashisms48= verifyNot checkBashisms "#!/bin/sh\necho $LINENO" -- >>> prop $ verify checkBashisms "#!/bin/dash\necho foo 42>/dev/null"
prop_checkBashisms49= verify checkBashisms "#!/bin/dash\necho $MACHTYPE" -- >>> prop $ verifyNot checkBashisms "#!/bin/sh\necho $LINENO"
prop_checkBashisms50= verify checkBashisms "#!/bin/sh\ncmd >& file" -- >>> prop $ verify checkBashisms "#!/bin/dash\necho $MACHTYPE"
prop_checkBashisms51= verifyNot checkBashisms "#!/bin/sh\ncmd 2>&1" -- >>> prop $ verify checkBashisms "#!/bin/sh\ncmd >& file"
prop_checkBashisms52= verifyNot checkBashisms "#!/bin/sh\ncmd >&2" -- >>> prop $ verifyNot checkBashisms "#!/bin/sh\ncmd 2>&1"
prop_checkBashisms53= verifyNot checkBashisms "#!/bin/sh\nprintf -- -f\n" -- >>> prop $ verifyNot checkBashisms "#!/bin/sh\ncmd >&2"
prop_checkBashisms54= verify checkBashisms "#!/bin/sh\nfoo+=bar" -- >>> prop $ verifyNot checkBashisms "#!/bin/sh\nprintf -- -f\n"
prop_checkBashisms55= verify checkBashisms "#!/bin/sh\necho ${@%foo}" -- >>> prop $ verify checkBashisms "#!/bin/sh\nfoo+=bar"
prop_checkBashisms56= verifyNot checkBashisms "#!/bin/sh\necho ${##}" -- >>> prop $ verify checkBashisms "#!/bin/sh\necho ${@%foo}"
-- >>> prop $ verifyNot checkBashisms "#!/bin/sh\necho ${##}"
checkBashisms = ForShell [Sh, Dash] $ \t -> do checkBashisms = ForShell [Sh, Dash] $ \t -> do
params <- ask params <- ask
kludge params t kludge params t
@@ -317,8 +316,9 @@ checkBashisms = ForShell [Sh, Dash] $ \t -> do
Assignment (_, _, name, _) -> name == var Assignment (_, _, name, _) -> name == var
_ -> False _ -> 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 checkEchoSed = ForShell [Bash, Ksh] f
where where
f (T_Pipeline id _ [a, b]) = f (T_Pipeline id _ [a, b]) =
@@ -344,10 +344,11 @@ checkEchoSed = ForShell [Bash, Ksh] f
f _ = return () f _ = return ()
prop_checkBraceExpansionVars1 = verify checkBraceExpansionVars "echo {1..$n}" -- |
prop_checkBraceExpansionVars2 = verifyNot checkBraceExpansionVars "echo {1,3,$n}" -- >>> prop $ verify checkBraceExpansionVars "echo {1..$n}"
prop_checkBraceExpansionVars3 = verify checkBraceExpansionVars "eval echo DSC{0001..$n}.jpg" -- >>> prop $ verifyNot checkBraceExpansionVars "echo {1,3,$n}"
prop_checkBraceExpansionVars4 = verify checkBraceExpansionVars "echo {$i..100}" -- >>> prop $ verify checkBraceExpansionVars "eval echo DSC{0001..$n}.jpg"
-- >>> prop $ verify checkBraceExpansionVars "echo {$i..100}"
checkBraceExpansionVars = ForShell [Bash] f checkBraceExpansionVars = ForShell [Bash] f
where where
f t@(T_BraceExpansion id list) = mapM_ check list f t@(T_BraceExpansion id list) = mapM_ check list
@@ -372,12 +373,13 @@ checkBraceExpansionVars = ForShell [Bash] f
return $ isJust cmd && fromJust cmd `isUnqualifiedCommand` "eval" return $ isJust cmd && fromJust cmd `isUnqualifiedCommand` "eval"
prop_checkMultiDimensionalArrays1 = verify checkMultiDimensionalArrays "foo[a][b]=3" -- |
prop_checkMultiDimensionalArrays2 = verifyNot checkMultiDimensionalArrays "foo[a]=3" -- >>> prop $ verify checkMultiDimensionalArrays "foo[a][b]=3"
prop_checkMultiDimensionalArrays3 = verify checkMultiDimensionalArrays "foo=( [a][b]=c )" -- >>> prop $ verifyNot checkMultiDimensionalArrays "foo[a]=3"
prop_checkMultiDimensionalArrays4 = verifyNot checkMultiDimensionalArrays "foo=( [a]=c )" -- >>> prop $ verify checkMultiDimensionalArrays "foo=( [a][b]=c )"
prop_checkMultiDimensionalArrays5 = verify checkMultiDimensionalArrays "echo ${foo[bar][baz]}" -- >>> prop $ verifyNot checkMultiDimensionalArrays "foo=( [a]=c )"
prop_checkMultiDimensionalArrays6 = verifyNot checkMultiDimensionalArrays "echo ${foo[bar]}" -- >>> prop $ verify checkMultiDimensionalArrays "echo ${foo[bar][baz]}"
-- >>> prop $ verifyNot checkMultiDimensionalArrays "echo ${foo[bar]}"
checkMultiDimensionalArrays = ForShell [Bash] f checkMultiDimensionalArrays = ForShell [Bash] f
where where
f token = f token =
@@ -392,16 +394,17 @@ checkMultiDimensionalArrays = ForShell [Bash] f
re = mkRegex "^\\[.*\\]\\[.*\\]" -- Fixme, this matches ${foo:- [][]} and such as well re = mkRegex "^\\[.*\\]\\[.*\\]" -- Fixme, this matches ${foo:- [][]} and such as well
isMultiDim t = getBracedModifier (bracedString t) `matches` re 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 $ verify checkPS1Assignments "PS1='\\033[1;35m\\$ '"
prop_checkPSf2 = verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '" -- >>> prop $ verify checkPS1Assignments "export PS1='\\033[1;35m\\$ '"
prop_checkPS13 = verify checkPS1Assignments "PS1=$'\\x1b[c '" -- >>> prop $ verify checkPS1Assignments "PS1='\\h \\e[0m\\$ '"
prop_checkPS14 = verify checkPS1Assignments "PS1=$'\\e[3m; '" -- >>> prop $ verify checkPS1Assignments "PS1=$'\\x1b[c '"
prop_checkPS14a= verify checkPS1Assignments "export PS1=$'\\e[3m; '" -- >>> prop $ verify checkPS1Assignments "PS1=$'\\e[3m; '"
prop_checkPS15 = verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '" -- >>> prop $ verify checkPS1Assignments "export PS1=$'\\e[3m; '"
prop_checkPS16 = verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '" -- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\033[1;35m\\]\\$ '"
prop_checkPS17 = verifyNot checkPS1Assignments "PS1='e033x1B'" -- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\e1m\\e[1m\\]\\$ '"
prop_checkPS18 = verifyNot checkPS1Assignments "PS1='\\[\\e\\]'" -- >>> prop $ verifyNot checkPS1Assignments "PS1='e033x1B'"
-- >>> prop $ verifyNot checkPS1Assignments "PS1='\\[\\e\\]'"
checkPS1Assignments = ForShell [Bash] f checkPS1Assignments = ForShell [Bash] f
where where
f token = case token of f token = case token of
@@ -417,7 +420,3 @@ checkPS1Assignments = ForShell [Bash] f
isJust $ matchRegex escapeRegex unenclosed isJust $ matchRegex escapeRegex unenclosed
enclosedRegex = mkRegex "\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager enclosedRegex = mkRegex "\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager
escapeRegex = mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033" escapeRegex = mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033"
return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])

View File

@@ -95,7 +95,7 @@ outputWiki errRef = do
where where
showErr (_, code, msg) = showErr (_, code, msg) =
putStrLn $ " " ++ wikiLink ++ "SC" ++ show code ++ " -- " ++ shorten msg putStrLn $ " " ++ wikiLink ++ "SC" ++ show code ++ " -- " ++ shorten msg
limit = 40 limit = 36
shorten msg = shorten msg =
if length msg < limit if length msg < limit
then msg then msg
@@ -130,8 +130,7 @@ outputForFile color sys comments = do
putStrLn (color "source" line) putStrLn (color "source" line)
mapM_ (\c -> putStrLn (color (severityText c) $ cuteIndent c)) commentsForLine mapM_ (\c -> putStrLn (color (severityText c) $ cuteIndent c)) commentsForLine
putStrLn "" putStrLn ""
-- FIXME: Enable when reasonably stable showFixedString color comments lineNum line
-- showFixedString color comments lineNum line
) groups ) groups
hasApplicableFix lineNum comment = fromMaybe False $ do hasApplicableFix lineNum comment = fromMaybe False $ do

View File

@@ -17,11 +17,10 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE MultiWayIf #-}
module ShellCheck.Parser (parseScript, runTests) where module ShellCheck.Parser (parseScript) where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
@@ -48,7 +47,9 @@ import qualified Control.Monad.Reader as Mr
import qualified Control.Monad.State as Ms import qualified Control.Monad.State as Ms
import qualified Data.Map as Map 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 SCBase m = Mr.ReaderT (Environment m) (Ms.StateT SystemState m)
type SCParser m v = ParsecT String UserState (SCBase m) v type SCParser m v = ParsecT String UserState (SCBase m) v
@@ -87,7 +88,8 @@ extglobStart = oneOf extglobStartChars
unicodeDoubleQuotes = "\x201C\x201D\x2033\x2036" unicodeDoubleQuotes = "\x201C\x201D\x2033\x2036"
unicodeSingleQuotes = "\x2018\x2019" unicodeSingleQuotes = "\x2018\x2019"
prop_spacing = isOk spacing " \\\n # Comment" -- |
-- >>> prop $ isOk spacing " \\\n # Comment"
spacing = do spacing = do
x <- many (many1 linewhitespace <|> try (string "\\\n" >> return "")) x <- many (many1 linewhitespace <|> try (string "\\\n" >> return ""))
optional readComment optional readComment
@@ -98,9 +100,10 @@ spacing1 = do
when (null spacing) $ fail "Expected whitespace" when (null spacing) $ fail "Expected whitespace"
return spacing return spacing
prop_allspacing = isOk allspacing "#foo" -- |
prop_allspacing2 = isOk allspacing " #foo\n # bar\n#baz\n" -- >>> prop $ isOk allspacing "#foo"
prop_allspacing3 = isOk allspacing "#foo\n#bar\n#baz\n" -- >>> prop $ isOk allspacing " #foo\n # bar\n#baz\n"
-- >>> prop $ isOk allspacing "#foo\n#bar\n#baz\n"
allspacing = do allspacing = do
s <- spacing s <- spacing
more <- option False (linefeed >> return True) more <- option False (linefeed >> return True)
@@ -673,29 +676,30 @@ readConditionContents single =
readCondContents = readCondOr readCondContents = readCondOr
prop_a1 = isOk readArithmeticContents " n++ + ++c" -- |
prop_a2 = isOk readArithmeticContents "$N*4-(3,2)" -- >>> prop $ isOk readArithmeticContents " n++ + ++c"
prop_a3 = isOk readArithmeticContents "n|=2<<1" -- >>> prop $ isOk readArithmeticContents "$N*4-(3,2)"
prop_a4 = isOk readArithmeticContents "n &= 2 **3" -- >>> prop $ isOk readArithmeticContents "n|=2<<1"
prop_a5 = isOk readArithmeticContents "1 |= 4 && n >>= 4" -- >>> prop $ isOk readArithmeticContents "n &= 2 **3"
prop_a6 = isOk readArithmeticContents " 1 | 2 ||3|4" -- >>> prop $ isOk readArithmeticContents "1 |= 4 && n >>= 4"
prop_a7 = isOk readArithmeticContents "3*2**10" -- >>> prop $ isOk readArithmeticContents " 1 | 2 ||3|4"
prop_a8 = isOk readArithmeticContents "3" -- >>> prop $ isOk readArithmeticContents "3*2**10"
prop_a9 = isOk readArithmeticContents "a^!-b" -- >>> prop $ isOk readArithmeticContents "3"
prop_a10= isOk readArithmeticContents "! $?" -- >>> prop $ isOk readArithmeticContents "a^!-b"
prop_a11= isOk readArithmeticContents "10#08 * 16#f" -- >>> prop $ isOk readArithmeticContents "! $?"
prop_a12= isOk readArithmeticContents "\"$((3+2))\" + '37'" -- >>> prop $ isOk readArithmeticContents "10#08 * 16#f"
prop_a13= isOk readArithmeticContents "foo[9*y+x]++" -- >>> prop $ isOk readArithmeticContents "\"$((3+2))\" + '37'"
prop_a14= isOk readArithmeticContents "1+`echo 2`" -- >>> prop $ isOk readArithmeticContents "foo[9*y+x]++"
prop_a15= isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4" -- >>> prop $ isOk readArithmeticContents "1+`echo 2`"
prop_a16= isOk readArithmeticContents "$foo$bar" -- >>> prop $ isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4"
prop_a17= isOk readArithmeticContents "i<(0+(1+1))" -- >>> prop $ isOk readArithmeticContents "$foo$bar"
prop_a18= isOk readArithmeticContents "a?b:c" -- >>> prop $ isOk readArithmeticContents "i<(0+(1+1))"
prop_a19= isOk readArithmeticContents "\\\n3 +\\\n 2" -- >>> prop $ isOk readArithmeticContents "a?b:c"
prop_a20= isOk readArithmeticContents "a ? b ? c : d : e" -- >>> prop $ isOk readArithmeticContents "\\\n3 +\\\n 2"
prop_a21= isOk readArithmeticContents "a ? b : c ? d : e" -- >>> prop $ isOk readArithmeticContents "a ? b ? c : d : e"
prop_a22= isOk readArithmeticContents "!!a" -- >>> prop $ isOk readArithmeticContents "a ? b : c ? d : e"
prop_a23= isOk readArithmeticContents "~0" -- >>> prop $ isOk readArithmeticContents "!!a"
-- >>> prop $ isOk readArithmeticContents "~0"
readArithmeticContents :: Monad m => SCParser m Token readArithmeticContents :: Monad m => SCParser m Token
readArithmeticContents = readArithmeticContents =
readSequence readSequence
@@ -876,33 +880,34 @@ readArithmeticContents =
prop_readCondition = isOk readCondition "[ \\( a = b \\) -a \\( c = d \\) ]" -- |
prop_readCondition2 = isOk readCondition "[[ (a = b) || (c = d) ]]" -- >>> prop $ isOk readCondition "[ \\( a = b \\) -a \\( c = d \\) ]"
prop_readCondition3 = isOk readCondition "[[ $c = [[:alpha:].~-] ]]" -- >>> prop $ isOk readCondition "[[ (a = b) || (c = d) ]]"
prop_readCondition4 = isOk readCondition "[[ $c =~ *foo* ]]" -- >>> prop $ isOk readCondition "[[ $c = [[:alpha:].~-] ]]"
prop_readCondition5 = isOk readCondition "[[ $c =~ f( ]] )* ]]" -- >>> prop $ isOk readCondition "[[ $c =~ *foo* ]]"
prop_readCondition5a = isOk readCondition "[[ $c =~ a(b) ]]" -- >>> prop $ isOk readCondition "[[ $c =~ f( ]] )* ]]"
prop_readCondition5b = isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]" -- >>> prop $ isOk readCondition "[[ $c =~ a(b) ]]"
prop_readCondition6 = isOk readCondition "[[ $c =~ ^[yY]$ ]]" -- >>> prop $ isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]"
prop_readCondition7 = isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]" -- >>> prop $ isOk readCondition "[[ $c =~ ^[yY]$ ]]"
prop_readCondition8 = isOk readCondition "[[ $l =~ ogg|flac ]]" -- >>> prop $ isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]"
prop_readCondition9 = isOk readCondition "[ foo -a -f bar ]" -- >>> prop $ isOk readCondition "[[ $l =~ ogg|flac ]]"
prop_readCondition10 = isOk readCondition "[[\na == b\n||\nc == d ]]" -- >>> prop $ isOk readCondition "[ foo -a -f bar ]"
prop_readCondition10a= isOk readCondition "[[\na == b ||\nc == d ]]" -- >>> prop $ isOk readCondition "[[\na == b\n||\nc == d ]]"
prop_readCondition10b= isOk readCondition "[[ a == b\n||\nc == d ]]" -- >>> prop $ isOk readCondition "[[\na == b ||\nc == d ]]"
prop_readCondition11 = isOk readCondition "[[ a == b ||\n c == d ]]" -- >>> prop $ isOk readCondition "[[ a == b\n||\nc == d ]]"
prop_readCondition12 = isWarning readCondition "[ a == b \n -o c == d ]" -- >>> prop $ isOk readCondition "[[ a == b ||\n c == d ]]"
prop_readCondition13 = isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]" -- >>> prop $ isWarning readCondition "[ a == b \n -o c == d ]"
prop_readCondition14 = isOk readCondition "[ foo '>' bar ]" -- >>> prop $ isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]"
prop_readCondition15 = isOk readCondition "[ foo \">=\" bar ]" -- >>> prop $ isOk readCondition "[ foo '>' bar ]"
prop_readCondition16 = isOk readCondition "[ foo \\< bar ]" -- >>> prop $ isOk readCondition "[ foo \">=\" bar ]"
prop_readCondition17 = isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]" -- >>> prop $ isOk readCondition "[ foo \\< bar ]"
prop_readCondition18 = isOk readCondition "[ ]" -- >>> prop $ isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]"
prop_readCondition19 = isOk readCondition "[ '(' x \")\" ]" -- >>> prop $ isOk readCondition "[ ]"
prop_readCondition20 = isOk readCondition "[[ echo_rc -eq 0 ]]" -- >>> prop $ isOk readCondition "[ '(' x \")\" ]"
prop_readCondition21 = isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]" -- >>> prop $ isOk readCondition "[[ echo_rc -eq 0 ]]"
prop_readCondition22 = isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]" -- >>> prop $ isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]"
prop_readCondition23 = isOk readCondition "[[ -v arr[$var] ]]" -- >>> prop $ isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]"
-- >>> prop $ isOk readCondition "[[ -v arr[$var] ]]"
readCondition = called "test expression" $ do readCondition = called "test expression" $ do
opos <- getPosition opos <- getPosition
start <- startSpan start <- startSpan
@@ -940,12 +945,13 @@ readAnnotationPrefix = do
many linewhitespace many linewhitespace
string "shellcheck" string "shellcheck"
prop_readAnnotation1 = isOk readAnnotation "# shellcheck disable=1234,5678\n" -- |
prop_readAnnotation2 = isOk readAnnotation "# shellcheck disable=SC1234 disable=SC5678\n" -- >>> prop $ isOk readAnnotation "# shellcheck disable=1234,5678\n"
prop_readAnnotation3 = isOk readAnnotation "# shellcheck disable=SC1234 source=/dev/null disable=SC5678\n" -- >>> prop $ isOk readAnnotation "# shellcheck disable=SC1234 disable=SC5678\n"
prop_readAnnotation4 = isWarning readAnnotation "# shellcheck cats=dogs disable=SC1234\n" -- >>> prop $ isOk readAnnotation "# shellcheck disable=SC1234 source=/dev/null disable=SC5678\n"
prop_readAnnotation5 = isOk readAnnotation "# shellcheck disable=SC2002 # All cats are precious\n" -- >>> prop $ isWarning readAnnotation "# shellcheck cats=dogs disable=SC1234\n"
prop_readAnnotation6 = isOk readAnnotation "# shellcheck disable=SC1234 # shellcheck foo=bar\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 readAnnotation = called "shellcheck directive" $ do
try readAnnotationPrefix try readAnnotationPrefix
many1 linewhitespace many1 linewhitespace
@@ -1002,18 +1008,19 @@ readAnyComment = do
char '#' char '#'
many $ noneOf "\r\n" many $ noneOf "\r\n"
prop_readNormalWord = isOk readNormalWord "'foo'\"bar\"{1..3}baz$(lol)" -- |
prop_readNormalWord2 = isOk readNormalWord "foo**(foo)!!!(@@(bar))" -- >>> prop $ isOk readNormalWord "'foo'\"bar\"{1..3}baz$(lol)"
prop_readNormalWord3 = isOk readNormalWord "foo#" -- >>> prop $ isOk readNormalWord "foo**(foo)!!!(@@(bar))"
prop_readNormalWord4 = isOk readNormalWord "$\"foo\"$'foo\nbar'" -- >>> prop $ isOk readNormalWord "foo#"
prop_readNormalWord5 = isWarning readNormalWord "${foo}}" -- >>> prop $ isOk readNormalWord "$\"foo\"$'foo\nbar'"
prop_readNormalWord6 = isOk readNormalWord "foo/{}" -- >>> prop $ isWarning readNormalWord "${foo}}"
prop_readNormalWord7 = isOk readNormalWord "foo\\\nbar" -- >>> prop $ isOk readNormalWord "foo/{}"
prop_readNormalWord8 = isWarning readSubshell "(foo\\ \nbar)" -- >>> prop $ isOk readNormalWord "foo\\\nbar"
prop_readNormalWord9 = isOk readSubshell "(foo\\ ;\nbar)" -- >>> prop $ isWarning readSubshell "(foo\\ \nbar)"
prop_readNormalWord10 = isWarning readNormalWord "\x201Chello\x201D" -- >>> prop $ isOk readSubshell "(foo\\ ;\nbar)"
prop_readNormalWord11 = isWarning readNormalWord "\x2018hello\x2019" -- >>> prop $ isWarning readNormalWord "\x201Chello\x201D"
prop_readNormalWord12 = isWarning readNormalWord "hello\x2018" -- >>> prop $ isWarning readNormalWord "\x2018hello\x2019"
-- >>> prop $ isWarning readNormalWord "hello\x2018"
readNormalWord = readNormalishWord "" readNormalWord = readNormalishWord ""
readNormalishWord end = do readNormalishWord end = do
@@ -1111,9 +1118,10 @@ readParamSubSpecialChar = do
id <- endSpan start id <- endSpan start
return $ T_ParamSubSpecialChar id x return $ T_ParamSubSpecialChar id x
prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)" -- |
prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )" -- >>> prop $ isOk readProcSub "<(echo test | wc -l)"
prop_readProcSub3 = isOk readProcSub "<( # nothing here \n)" -- >>> prop $ isOk readProcSub "<( if true; then true; fi )"
-- >>> prop $ isOk readProcSub "<( # nothing here \n)"
readProcSub = called "process substitution" $ do readProcSub = called "process substitution" $ do
start <- startSpan start <- startSpan
dir <- try $ do dir <- try $ do
@@ -1126,13 +1134,14 @@ readProcSub = called "process substitution" $ do
id <- endSpan start id <- endSpan start
return $ T_ProcSub id dir list return $ T_ProcSub id dir list
prop_readSingleQuoted = isOk readSingleQuoted "'foo bar'" -- |
prop_readSingleQuoted2 = isWarning readSingleQuoted "'foo bar\\'" -- >>> prop $ isOk readSingleQuoted "'foo bar'"
prop_readSingleQuoted4 = isWarning readNormalWord "'it's" -- >>> prop $ isWarning readSingleQuoted "'foo bar\\'"
prop_readSingleQuoted5 = isWarning readSimpleCommand "foo='bar\ncow 'arg" -- >>> prop $ isWarning readNormalWord "'it's"
prop_readSingleQuoted6 = isOk readSimpleCommand "foo='bar cow 'arg" -- >>> prop $ isWarning readSimpleCommand "foo='bar\ncow 'arg"
prop_readSingleQuoted7 = isOk readSingleQuoted "'foo\x201C\&bar'" -- >>> prop $ isOk readSimpleCommand "foo='bar cow 'arg"
prop_readSingleQuoted8 = isWarning readSingleQuoted "'foo\x2018\&bar'" -- >>> prop $ isOk readSingleQuoted "'foo\x201C\&bar'"
-- >>> prop $ isWarning readSingleQuoted "'foo\x2018\&bar'"
readSingleQuoted = called "single quoted string" $ do readSingleQuoted = called "single quoted string" $ do
start <- startSpan start <- startSpan
startPos <- getPosition startPos <- getPosition
@@ -1174,14 +1183,15 @@ readSingleQuotedPart =
return [x] return [x]
prop_readBackTicked = isOk (readBackTicked False) "`ls *.mp3`" -- |
prop_readBackTicked2 = isOk (readBackTicked False) "`grep \"\\\"\"`" -- >>> prop $ isOk (readBackTicked False) "`ls *.mp3`"
prop_readBackTicked3 = isWarning (readBackTicked False) "´grep \"\\\"\"´" -- >>> prop $ isOk (readBackTicked False) "`grep \"\\\"\"`"
prop_readBackTicked4 = isOk readSimpleCommand "`echo foo\necho bar`" -- >>> prop $ isWarning (readBackTicked False) "´grep \"\\\"\"´"
prop_readBackTicked5 = isOk readSimpleCommand "echo `foo`bar" -- >>> prop $ isOk readSimpleCommand "`echo foo\necho bar`"
prop_readBackTicked6 = isWarning readSimpleCommand "echo `foo\necho `bar" -- >>> prop $ isOk readSimpleCommand "echo `foo`bar"
prop_readBackTicked7 = isOk readSimpleCommand "`#inline comment`" -- >>> prop $ isWarning readSimpleCommand "echo `foo\necho `bar"
prop_readBackTicked8 = isOk readSimpleCommand "echo `#comment` \\\nbar baz" -- >>> prop $ isOk readSimpleCommand "`#inline comment`"
-- >>> prop $ isOk readSimpleCommand "echo `#comment` \\\nbar baz"
readQuotedBackTicked = readBackTicked True readQuotedBackTicked = readBackTicked True
readUnquotedBackTicked = readBackTicked False readUnquotedBackTicked = readBackTicked False
readBackTicked quoted = called "backtick expansion" $ do readBackTicked quoted = called "backtick expansion" $ do
@@ -1247,15 +1257,16 @@ parseForgettingContext alsoOnSuccess parser = do
Ms.put c Ms.put c
fail "" fail ""
prop_readDoubleQuoted = isOk readDoubleQuoted "\"Hello $FOO\"" -- |
prop_readDoubleQuoted2 = isOk readDoubleQuoted "\"$'\"" -- >>> prop $ isOk readDoubleQuoted "\"Hello $FOO\""
prop_readDoubleQuoted3 = isOk readDoubleQuoted "\"\x2018hello\x2019\"" -- >>> prop $ isOk readDoubleQuoted "\"$'\""
prop_readDoubleQuoted4 = isWarning readSimpleCommand "\"foo\nbar\"foo" -- >>> prop $ isOk readDoubleQuoted "\"\x2018hello\x2019\""
prop_readDoubleQuoted5 = isOk readSimpleCommand "lol \"foo\nbar\" etc" -- >>> prop $ isWarning readSimpleCommand "\"foo\nbar\"foo"
prop_readDoubleQuoted6 = isOk readSimpleCommand "echo \"${ ls; }\"" -- >>> prop $ isOk readSimpleCommand "lol \"foo\nbar\" etc"
prop_readDoubleQuoted7 = isOk readSimpleCommand "echo \"${ ls;}bar\"" -- >>> prop $ isOk readSimpleCommand "echo \"${ ls; }\""
prop_readDoubleQuoted8 = isWarning readDoubleQuoted "\"\x201Chello\x201D\"" -- >>> prop $ isOk readSimpleCommand "echo \"${ ls;}bar\""
prop_readDoubleQuoted10 = isOk readDoubleQuoted "\"foo\\\\n\"" -- >>> prop $ isWarning readDoubleQuoted "\"\x201Chello\x201D\""
-- >>> prop $ isOk readDoubleQuoted "\"foo\\\\n\""
readDoubleQuoted = called "double quoted string" $ do readDoubleQuoted = called "double quoted string" $ do
start <- startSpan start <- startSpan
startPos <- getPosition startPos <- getPosition
@@ -1308,14 +1319,15 @@ readNormalLiteral end = do
id <- endSpan start id <- endSpan start
return $ T_Literal id (concat s) return $ T_Literal id (concat s)
prop_readGlob1 = isOk readGlob "*" -- |
prop_readGlob2 = isOk readGlob "[^0-9]" -- >>> prop $ isOk readGlob "*"
prop_readGlob3 = isOk readGlob "[a[:alpha:]]" -- >>> prop $ isOk readGlob "[^0-9]"
prop_readGlob4 = isOk readGlob "[[:alnum:]]" -- >>> prop $ isOk readGlob "[a[:alpha:]]"
prop_readGlob5 = isOk readGlob "[^[:alpha:]1-9]" -- >>> prop $ isOk readGlob "[[:alnum:]]"
prop_readGlob6 = isOk readGlob "[\\|]" -- >>> prop $ isOk readGlob "[^[:alpha:]1-9]"
prop_readGlob7 = isOk readGlob "[^[]" -- >>> prop $ isOk readGlob "[\\|]"
prop_readGlob8 = isOk readGlob "[*?]" -- >>> prop $ isOk readGlob "[^[]"
-- >>> prop $ isOk readGlob "[*?]"
readGlob = readExtglob <|> readSimple <|> readClass <|> readGlobbyLiteral readGlob = readExtglob <|> readSimple <|> readClass <|> readGlobbyLiteral
where where
readSimple = do 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)." 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 $ isOk readExtglob "!(*.mp3)"
prop_readExtglob4 = isOk readExtglob "+(foo \\) bar)" -- >>> prop $ isOk readExtglob "!(*.mp3|*.wmv)"
prop_readExtglob5 = isOk readExtglob "+(!(foo *(bar)))" -- >>> prop $ isOk readExtglob "+(foo \\) bar)"
prop_readExtglob6 = isOk readExtglob "*(((||))|())" -- >>> prop $ isOk readExtglob "+(!(foo *(bar)))"
prop_readExtglob7 = isOk readExtglob "*(<>)" -- >>> prop $ isOk readExtglob "*(((||))|())"
prop_readExtglob8 = isOk readExtglob "@(|*())" -- >>> prop $ isOk readExtglob "*(<>)"
-- >>> prop $ isOk readExtglob "@(|*())"
readExtglob = called "extglob" $ do readExtglob = called "extglob" $ do
start <- startSpan start <- startSpan
c <- try $ do c <- try $ do
@@ -1465,14 +1478,15 @@ readGenericEscaped = do
x <- anyChar x <- anyChar
return $ if x == '\n' then [] else ['\\', x] return $ if x == '\n' then [] else ['\\', x]
prop_readBraced = isOk readBraced "{1..4}" -- |
prop_readBraced2 = isOk readBraced "{foo,bar,\"baz lol\"}" -- >>> prop $ isOk readBraced "{1..4}"
prop_readBraced3 = isOk readBraced "{1,\\},2}" -- >>> prop $ isOk readBraced "{foo,bar,\"baz lol\"}"
prop_readBraced4 = isOk readBraced "{1,{2,3}}" -- >>> prop $ isOk readBraced "{1,\\},2}"
prop_readBraced5 = isOk readBraced "{JP{,E}G,jp{,e}g}" -- >>> prop $ isOk readBraced "{1,{2,3}}"
prop_readBraced6 = isOk readBraced "{foo,bar,$((${var}))}" -- >>> prop $ isOk readBraced "{JP{,E}G,jp{,e}g}"
prop_readBraced7 = isNotOk readBraced "{}" -- >>> prop $ isOk readBraced "{foo,bar,$((${var}))}"
prop_readBraced8 = isNotOk readBraced "{foo}" -- >>> prop $ isNotOk readBraced "{}"
-- >>> prop $ isNotOk readBraced "{foo}"
readBraced = try braceExpansion readBraced = try braceExpansion
where where
braceExpansion = braceExpansion =
@@ -1512,9 +1526,10 @@ readDoubleQuotedDollar = do
readDollarExp <|> readDollarLonely readDollarExp <|> readDollarLonely
prop_readDollarExpression1 = isOk readDollarExpression "$(((1) && 3))" -- |
prop_readDollarExpression2 = isWarning readDollarExpression "$(((1)) && 3)" -- >>> prop $ isOk readDollarExpression "$(((1) && 3))"
prop_readDollarExpression3 = isWarning readDollarExpression "$((\"$@\" &); foo;)" -- >>> prop $ isWarning readDollarExpression "$(((1)) && 3)"
-- >>> prop $ isWarning readDollarExpression "$((\"$@\" &); foo;)"
readDollarExpression :: Monad m => SCParser m Token readDollarExpression :: Monad m => SCParser m Token
readDollarExpression = do readDollarExpression = do
ensureDollar ensureDollar
@@ -1525,7 +1540,8 @@ readDollarExp = arithmetic <|> readDollarExpansion <|> readDollarBracket <|> rea
arithmetic = readAmbiguous "$((" readDollarArithmetic readDollarExpansion (\pos -> 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.") 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 readDollarSingleQuote = called "$'..' expression" $ do
start <- startSpan start <- startSpan
try $ string "$'" try $ string "$'"
@@ -1534,7 +1550,8 @@ readDollarSingleQuote = called "$'..' expression" $ do
id <- endSpan start id <- endSpan start
return $ T_DollarSingleQuoted id str return $ T_DollarSingleQuoted id str
prop_readDollarDoubleQuote = isOk readDollarDoubleQuote "$\"hello\"" -- |
-- >>> prop $ isOk readDollarDoubleQuote "$\"hello\""
readDollarDoubleQuote = do readDollarDoubleQuote = do
lookAhead . try $ string "$\"" lookAhead . try $ string "$\""
start <- startSpan start <- startSpan
@@ -1545,8 +1562,9 @@ readDollarDoubleQuote = do
id <- endSpan start id <- endSpan start
return $ T_DollarDoubleQuoted id x 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 readDollarArithmetic = called "$((..)) expression" $ do
start <- startSpan start <- startSpan
try (string "$((") try (string "$((")
@@ -1565,7 +1583,8 @@ readDollarBracket = called "$[..] expression" $ do
id <- endSpan start id <- endSpan start
return (T_DollarBracket id c) return (T_DollarBracket id c)
prop_readArithmeticExpression = isOk readArithmeticExpression "((a?b:c))" -- |
-- >>> prop $ isOk readArithmeticExpression "((a?b:c))"
readArithmeticExpression = called "((..)) command" $ do readArithmeticExpression = called "((..)) command" $ do
start <- startSpan start <- startSpan
try (string "((") try (string "((")
@@ -1588,8 +1607,9 @@ readAmbiguous prefix expected alternative warner = do
warner pos warner pos
return t 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 readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do
start <- startSpan start <- startSpan
try $ do try $ do
@@ -1601,10 +1621,11 @@ readDollarBraceCommandExpansion = called "ksh ${ ..; } command expansion" $ do
id <- endSpan start id <- endSpan start
return $ T_DollarBraceCommandExpansion id term return $ T_DollarBraceCommandExpansion id term
prop_readDollarBraced1 = isOk readDollarBraced "${foo//bar/baz}" -- |
prop_readDollarBraced2 = isOk readDollarBraced "${foo/'{cow}'}" -- >>> prop $ isOk readDollarBraced "${foo//bar/baz}"
prop_readDollarBraced3 = isOk readDollarBraced "${foo%%$(echo cow\\})}" -- >>> prop $ isOk readDollarBraced "${foo/'{cow}'}"
prop_readDollarBraced4 = isOk readDollarBraced "${foo#\\}}" -- >>> prop $ isOk readDollarBraced "${foo%%$(echo cow\\})}"
-- >>> prop $ isOk readDollarBraced "${foo#\\}}"
readDollarBraced = called "parameter expansion" $ do readDollarBraced = called "parameter expansion" $ do
start <- startSpan start <- startSpan
try (string "${") try (string "${")
@@ -1613,9 +1634,10 @@ readDollarBraced = called "parameter expansion" $ do
id <- endSpan start id <- endSpan start
return $ T_DollarBraced id word return $ T_DollarBraced id word
prop_readDollarExpansion1= isOk readDollarExpansion "$(echo foo; ls\n)" -- |
prop_readDollarExpansion2= isOk readDollarExpansion "$( )" -- >>> prop $ isOk readDollarExpansion "$(echo foo; ls\n)"
prop_readDollarExpansion3= isOk readDollarExpansion "$( command \n#comment \n)" -- >>> prop $ isOk readDollarExpansion "$( )"
-- >>> prop $ isOk readDollarExpansion "$( command \n#comment \n)"
readDollarExpansion = called "command expansion" $ do readDollarExpansion = called "command expansion" $ do
start <- startSpan start <- startSpan
try (string "$(") try (string "$(")
@@ -1624,12 +1646,12 @@ readDollarExpansion = called "command expansion" $ do
id <- endSpan start id <- endSpan start
return $ T_DollarExpansion id cmds return $ T_DollarExpansion id cmds
prop_readDollarVariable = isOk readDollarVariable "$@" -- |
prop_readDollarVariable2 = isOk (readDollarVariable >> anyChar) "$?!" -- >>> prop $ isOk readDollarVariable "$@"
prop_readDollarVariable3 = isWarning (readDollarVariable >> anyChar) "$10" -- >>> prop $ isOk (readDollarVariable >> anyChar) "$?!"
prop_readDollarVariable4 = isWarning (readDollarVariable >> string "[@]") "$arr[@]" -- >>> prop $ isWarning (readDollarVariable >> anyChar) "$10"
prop_readDollarVariable5 = isWarning (readDollarVariable >> string "[f") "$arr[f" -- >>> prop $ isWarning (readDollarVariable >> string "[@]") "$arr[@]"
-- >>> prop $ isWarning (readDollarVariable >> string "[f") "$arr[f"
readDollarVariable :: Monad m => SCParser m Token readDollarVariable :: Monad m => SCParser m Token
readDollarVariable = do readDollarVariable = do
start <- startSpan start <- startSpan
@@ -1678,25 +1700,26 @@ readDollarLonely = do
n <- lookAhead (anyChar <|> (eof >> return '_')) n <- lookAhead (anyChar <|> (eof >> return '_'))
return $ T_Literal id "$" return $ T_Literal id "$"
prop_readHereDoc = isOk readScript "cat << foo\nlol\ncow\nfoo" -- |
prop_readHereDoc2 = isNotOk readScript "cat <<- EOF\n cow\n EOF" -- >>> prop $ isOk readScript "cat << foo\nlol\ncow\nfoo"
prop_readHereDoc3 = isOk readScript "cat << foo\n$\"\nfoo" -- >>> prop $ isNotOk readScript "cat <<- EOF\n cow\n EOF"
prop_readHereDoc4 = isNotOk readScript "cat << foo\n`\nfoo" -- >>> prop $ isOk readScript "cat << foo\n$\"\nfoo"
prop_readHereDoc5 = isOk readScript "cat <<- !foo\nbar\n!foo" -- >>> prop $ isNotOk readScript "cat << foo\n`\nfoo"
prop_readHereDoc6 = isOk readScript "cat << foo\\ bar\ncow\nfoo bar" -- >>> prop $ isOk readScript "cat <<- !foo\nbar\n!foo"
prop_readHereDoc7 = isOk readScript "cat << foo\n\\$(f ())\nfoo" -- >>> prop $ isOk readScript "cat << foo\\ bar\ncow\nfoo bar"
prop_readHereDoc8 = isOk readScript "cat <<foo>>bar\netc\nfoo" -- >>> prop $ isOk readScript "cat << foo\n\\$(f ())\nfoo"
prop_readHereDoc9 = isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n" -- >>> prop $ isOk readScript "cat <<foo>>bar\netc\nfoo"
prop_readHereDoc10= isOk readScript "if true; then cat << foo << bar; fi\nfoo\nbar\n" -- >>> prop $ isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n"
prop_readHereDoc11= isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n" -- >>> prop $ isOk readScript "if true; then cat << foo << bar; fi\nfoo\nbar\n"
prop_readHereDoc12= isOk readScript "cat << foo|cat\nbar\nfoo" -- >>> prop $ isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n"
prop_readHereDoc13= isOk readScript "cat <<'#!'\nHello World\n#!\necho Done" -- >>> prop $ isOk readScript "cat << foo|cat\nbar\nfoo"
prop_readHereDoc14= isWarning readScript "cat << foo\nbar\nfoo \n" -- >>> prop $ isOk readScript "cat <<'#!'\nHello World\n#!\necho Done"
prop_readHereDoc15= isWarning readScript "cat <<foo\nbar\nfoo bar\nfoo" -- >>> prop $ isWarning readScript "cat << foo\nbar\nfoo \n"
prop_readHereDoc16= isOk readScript "cat <<- ' foo'\nbar\n foo\n" -- >>> prop $ isWarning readScript "cat <<foo\nbar\nfoo bar\nfoo"
prop_readHereDoc17= isWarning readScript "cat <<- ' foo'\nbar\n foo\n foo\n" -- >>> prop $ isOk readScript "cat <<- ' foo'\nbar\n foo\n"
prop_readHereDoc20= isWarning readScript "cat << foo\n foo\n()\nfoo\n" -- >>> prop $ isWarning readScript "cat <<- ' foo'\nbar\n foo\n foo\n"
prop_readHereDoc21= isOk readScript "# shellcheck disable=SC1039\ncat << foo\n foo\n()\nfoo\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 readHereDoc = called "here document" $ do
pos <- getPosition pos <- getPosition
try $ string "<<" try $ string "<<"
@@ -1864,7 +1887,8 @@ readIoDuplicate = try $ do
return $ str ++ dash return $ str ++ dash
prop_readIoFile = isOk readIoFile ">> \"$(date +%YYmmDD)\"" -- |
-- >>> prop $ isOk readIoFile ">> \"$(date +%YYmmDD)\""
readIoFile = called "redirection" $ do readIoFile = called "redirection" $ do
start <- startSpan start <- startSpan
op <- readIoFileOp op <- readIoFileOp
@@ -1884,13 +1908,14 @@ readIoSource = try $ do
lookAhead $ void readIoFileOp <|> void (string "<<") lookAhead $ void readIoFileOp <|> void (string "<<")
return x return x
prop_readIoRedirect = isOk readIoRedirect "3>&2" -- |
prop_readIoRedirect2 = isOk readIoRedirect "2> lol" -- >>> prop $ isOk readIoRedirect "3>&2"
prop_readIoRedirect3 = isOk readIoRedirect "4>&-" -- >>> prop $ isOk readIoRedirect "2> lol"
prop_readIoRedirect4 = isOk readIoRedirect "&> lol" -- >>> prop $ isOk readIoRedirect "4>&-"
prop_readIoRedirect5 = isOk readIoRedirect "{foo}>&2" -- >>> prop $ isOk readIoRedirect "&> lol"
prop_readIoRedirect6 = isOk readIoRedirect "{foo}<&-" -- >>> prop $ isOk readIoRedirect "{foo}>&2"
prop_readIoRedirect7 = isOk readIoRedirect "{foo}>&1-" -- >>> prop $ isOk readIoRedirect "{foo}<&-"
-- >>> prop $ isOk readIoRedirect "{foo}>&1-"
readIoRedirect = do readIoRedirect = do
start <- startSpan start <- startSpan
n <- readIoSource n <- readIoSource
@@ -1902,7 +1927,8 @@ readIoRedirect = do
readRedirectList = many1 readIoRedirect readRedirectList = many1 readIoRedirect
prop_readHereString = isOk readHereString "<<< \"Hello $world\"" -- |
-- >>> prop $ isOk readHereString "<<< \"Hello $world\""
readHereString = called "here string" $ do readHereString = called "here string" $ do
start <- startSpan start <- startSpan
try $ string "<<<" try $ string "<<<"
@@ -1916,15 +1942,17 @@ readNewlineList =
where where
checkBadBreak = optional $ do checkBadBreak = optional $ do
pos <- getPosition pos <- getPosition
try $ lookAhead (oneOf "|&") -- |, || or && try $ lookAhead (oneOf "|&") -- See if the next thing could be |, || or &&
parseProblemAt pos ErrorC 1133 "Unexpected start of line. If breaking lines, |/||/&& should be at the end of the previous one." parseProblemAt pos ErrorC 1133
"Unexpected start of line. If breaking lines, |/||/&& should be at the end of the previous one."
readLineBreak = optional readNewlineList readLineBreak = optional readNewlineList
prop_readSeparator1 = isWarning readScript "a &; b" -- |
prop_readSeparator2 = isOk readScript "a & b" -- >>> prop $ isWarning readScript "a &; b"
prop_readSeparator3 = isWarning readScript "a &amp; b" -- >>> prop $ isOk readScript "a & b"
prop_readSeparator4 = isWarning readScript "a &gt; file; b" -- >>> prop $ isWarning readScript "a &amp; b"
prop_readSeparator5 = isWarning readScript "curl https://example.com/?foo=moo&bar=cow" -- >>> prop $ isWarning readScript "a &gt; file; b"
-- >>> prop $ isWarning readScript "curl https://example.com/?foo=moo&bar=cow"
readSeparatorOp = do readSeparatorOp = do
notFollowedBy2 (void g_AND_IF <|> void readCaseSeparator) notFollowedBy2 (void g_AND_IF <|> void readCaseSeparator)
notFollowedBy2 (string "&>") notFollowedBy2 (string "&>")
@@ -1968,20 +1996,21 @@ readSeparator =
end <- getPosition end <- getPosition
return ('\n', (start, end)) return ('\n', (start, end))
prop_readSimpleCommand = isOk readSimpleCommand "echo test > file" -- |
prop_readSimpleCommand2 = isOk readSimpleCommand "cmd &> file" -- >>> prop $ isOk readSimpleCommand "echo test > file"
prop_readSimpleCommand3 = isOk readSimpleCommand "export foo=(bar baz)" -- >>> prop $ isOk readSimpleCommand "cmd &> file"
prop_readSimpleCommand4 = isOk readSimpleCommand "typeset -a foo=(lol)" -- >>> prop $ isOk readSimpleCommand "export foo=(bar baz)"
prop_readSimpleCommand5 = isOk readSimpleCommand "time if true; then echo foo; fi" -- >>> prop $ isOk readSimpleCommand "typeset -a foo=(lol)"
prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )" -- >>> prop $ isOk readSimpleCommand "time if true; then echo foo; fi"
prop_readSimpleCommand7 = isOk readSimpleCommand "\\ls" -- >>> prop $ isOk readSimpleCommand "time -p ( ls -l; )"
prop_readSimpleCommand8 = isWarning readSimpleCommand "// Lol" -- >>> prop $ isOk readSimpleCommand "\\ls"
prop_readSimpleCommand9 = isWarning readSimpleCommand "/* Lolbert */" -- >>> prop $ isWarning readSimpleCommand "// Lol"
prop_readSimpleCommand10 = isWarning readSimpleCommand "/**** Lolbert */" -- >>> prop $ isWarning readSimpleCommand "/* Lolbert */"
prop_readSimpleCommand11 = isOk readSimpleCommand "/\\* foo" -- >>> prop $ isWarning readSimpleCommand "/**** Lolbert */"
prop_readSimpleCommand12 = isWarning readSimpleCommand "elsif foo" -- >>> prop $ isOk readSimpleCommand "/\\* foo"
prop_readSimpleCommand13 = isWarning readSimpleCommand "ElseIf foo" -- >>> prop $ isWarning readSimpleCommand "elsif foo"
prop_readSimpleCommand14 = isWarning readSimpleCommand "elseif[$i==2]" -- >>> prop $ isWarning readSimpleCommand "ElseIf foo"
-- >>> prop $ isWarning readSimpleCommand "elseif[$i==2]"
readSimpleCommand = called "simple command" $ do readSimpleCommand = called "simple command" $ do
prefix <- option [] readCmdPrefix prefix <- option [] readCmdPrefix
skipAnnotationAndWarn skipAnnotationAndWarn
@@ -2107,9 +2136,10 @@ readSource t@(T_Redirecting _ _ (T_SimpleCommand cmdId _ (cmd:file:_))) = do
readSource t = return t 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 $ isOk readPipeline "! cat /etc/issue | grep -i ubuntu"
prop_readPipeline3 = isOk readPipeline "for f; do :; done|cat" -- >>> prop $ isWarning readPipeline "!cat /etc/issue | grep -i ubuntu"
-- >>> prop $ isOk readPipeline "for f; do :; done|cat"
readPipeline = do readPipeline = do
unexpecting "keyword/token" readKeyword unexpecting "keyword/token" readKeyword
do do
@@ -2119,9 +2149,10 @@ readPipeline = do
<|> <|>
readPipeSequence readPipeSequence
prop_readAndOr = isOk readAndOr "grep -i lol foo || exit 1" -- |
prop_readAndOr1 = isOk readAndOr "# shellcheck disable=1\nfoo" -- >>> prop $ isOk readAndOr "grep -i lol foo || exit 1"
prop_readAndOr2 = isOk readAndOr "# shellcheck disable=1\n# lol\n# shellcheck disable=3\nfoo" -- >>> prop $ isOk readAndOr "# shellcheck disable=1\nfoo"
-- >>> prop $ isOk readAndOr "# shellcheck disable=1\n# lol\n# shellcheck disable=3\nfoo"
readAndOr = do readAndOr = do
start <- startSpan start <- startSpan
apos <- getPosition apos <- getPosition
@@ -2149,7 +2180,8 @@ readTermOrNone = do
eof eof
return [] return []
prop_readTerm = isOk readTerm "time ( foo; bar; )" -- |
-- >>> prop $ isOk readTerm "time ( foo; bar; )"
readTerm = do readTerm = do
allspacing allspacing
m <- readAndOr m <- readAndOr
@@ -2220,11 +2252,12 @@ skipAnnotationAndWarn = optional $ do
parseProblem ErrorC 1126 "Place shellcheck directives before commands, not after." parseProblem ErrorC 1126 "Place shellcheck directives before commands, not after."
readAnyComment 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 $ isOk readIfClause "if false; then foo; elif true; then stuff; more stuff; else cows; fi"
prop_readIfClause3 = isWarning readIfClause "if false; then true; else; echo lol; fi" -- >>> prop $ isWarning readIfClause "if false; then; echo oo; fi"
prop_readIfClause4 = isWarning readIfClause "if false; then true; else if true; then echo lol; fi; fi" -- >>> prop $ isWarning readIfClause "if false; then true; else; echo lol; fi"
prop_readIfClause5 = isOk readIfClause "if false; then true; else\nif true; then echo lol; fi; 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 readIfClause = called "if expression" $ do
start <- startSpan start <- startSpan
pos <- getPosition pos <- getPosition
@@ -2299,7 +2332,8 @@ ifNextToken parser action =
try . lookAhead $ parser try . lookAhead $ parser
action 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 readSubshell = called "explicit subshell" $ do
start <- startSpan start <- startSpan
char '(' char '('
@@ -2310,9 +2344,10 @@ readSubshell = called "explicit subshell" $ do
id <- endSpan start id <- endSpan start
return $ T_Subshell id list return $ T_Subshell id list
prop_readBraceGroup = isOk readBraceGroup "{ a; b | c | d; e; }" -- |
prop_readBraceGroup2 = isWarning readBraceGroup "{foo;}" -- >>> prop $ isOk readBraceGroup "{ a; b | c | d; e; }"
prop_readBraceGroup3 = isOk readBraceGroup "{(foo)}" -- >>> prop $ isWarning readBraceGroup "{foo;}"
-- >>> prop $ isOk readBraceGroup "{(foo)}"
readBraceGroup = called "brace group" $ do readBraceGroup = called "brace group" $ do
start <- startSpan start <- startSpan
char '{' char '{'
@@ -2330,7 +2365,8 @@ readBraceGroup = called "brace group" $ do
id <- endSpan start id <- endSpan start
return $ T_BraceGroup id list 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 readWhileClause = called "while loop" $ do
start <- startSpan start <- startSpan
kwId <- getId <$> g_While kwId <- getId <$> g_While
@@ -2339,7 +2375,8 @@ readWhileClause = called "while loop" $ do
id <- endSpan start id <- endSpan start
return $ T_WhileExpression id condition statements 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 readUntilClause = called "until loop" $ do
start <- startSpan start <- startSpan
kwId <- getId <$> g_Until kwId <- getId <$> g_Until
@@ -2372,17 +2409,18 @@ readDoGroup kwId = do
return commands return commands
prop_readForClause = isOk readForClause "for f in *; do rm \"$f\"; done" -- |
prop_readForClause3 = isOk readForClause "for f; do foo; done" -- >>> prop $ isOk readForClause "for f in *; do rm \"$f\"; done"
prop_readForClause4 = isOk readForClause "for((i=0; i<10; i++)); do echo $i; done" -- >>> prop $ isOk readForClause "for f; do foo; done"
prop_readForClause5 = isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \necho $i\ndone" -- >>> prop $ isOk readForClause "for((i=0; i<10; i++)); do echo $i; done"
prop_readForClause6 = isOk readForClause "for ((;;))\ndo echo $i\ndone" -- >>> prop $ isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \necho $i\ndone"
prop_readForClause7 = isOk readForClause "for ((;;)) do echo $i\ndone" -- >>> prop $ isOk readForClause "for ((;;))\ndo echo $i\ndone"
prop_readForClause8 = isOk readForClause "for ((;;)) ; do echo $i\ndone" -- >>> prop $ isOk readForClause "for ((;;)) do echo $i\ndone"
prop_readForClause9 = isOk readForClause "for i do true; done" -- >>> prop $ isOk readForClause "for ((;;)) ; do echo $i\ndone"
prop_readForClause10= isOk readForClause "for ((;;)) { true; }" -- >>> prop $ isOk readForClause "for i do true; done"
prop_readForClause12= isWarning readForClause "for $a in *; do echo \"$a\"; done" -- >>> prop $ isOk readForClause "for ((;;)) { true; }"
prop_readForClause13= isOk readForClause "for foo\nin\\\n bar\\\n baz\ndo true; done" -- >>> 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 readForClause = called "for loop" $ do
pos <- getPosition pos <- getPosition
(T_For id) <- g_For (T_For id) <- g_For
@@ -2415,8 +2453,9 @@ readForClause = called "for loop" $ do
group <- readDoGroup id group <- readDoGroup id
return $ T_ForIn id name values group 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 readSelectClause = called "select loop" $ do
(T_Select id) <- g_Select (T_Select id) <- g_Select
spacing spacing
@@ -2445,11 +2484,12 @@ readInClause = do
return things 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 $ isOk readCaseClause "case foo in a ) lol; cow;; b|d) fooo; esac"
prop_readCaseClause3 = isOk readCaseClause "case foo\n in * ) echo bar & ;; esac" -- >>> prop $ isOk readCaseClause "case foo\n in * ) echo bar;; esac"
prop_readCaseClause4 = isOk readCaseClause "case foo\n in *) echo bar ;& bar) foo; esac" -- >>> prop $ isOk readCaseClause "case foo\n in * ) echo bar & ;; esac"
prop_readCaseClause5 = isOk readCaseClause "case foo\n in *) echo bar;;& foo) baz;; 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 readCaseClause = called "case expression" $ do
start <- startSpan start <- startSpan
g_Case g_Case
@@ -2493,18 +2533,19 @@ readCaseSeparator = choice [
lookAhead (readLineBreak >> g_Esac) >> return CaseBreak lookAhead (readLineBreak >> g_Esac) >> return CaseBreak
] ]
prop_readFunctionDefinition = isOk readFunctionDefinition "foo() { command foo --lol \"$@\"; }" -- |
prop_readFunctionDefinition1 = isOk readFunctionDefinition "foo (){ command foo --lol \"$@\"; }" -- >>> prop $ isOk readFunctionDefinition "foo() { command foo --lol \"$@\"; }"
prop_readFunctionDefinition4 = isWarning readFunctionDefinition "foo(a, b) { true; }" -- >>> prop $ isOk readFunctionDefinition "foo (){ command foo --lol \"$@\"; }"
prop_readFunctionDefinition5 = isOk readFunctionDefinition ":(){ :|:;}" -- >>> prop $ isWarning readFunctionDefinition "foo(a, b) { true; }"
prop_readFunctionDefinition6 = isOk readFunctionDefinition "?(){ foo; }" -- >>> prop $ isOk readFunctionDefinition ":(){ :|:;}"
prop_readFunctionDefinition7 = isOk readFunctionDefinition "..(){ cd ..; }" -- >>> prop $ isOk readFunctionDefinition "?(){ foo; }"
prop_readFunctionDefinition8 = isOk readFunctionDefinition "foo() (ls)" -- >>> prop $ isOk readFunctionDefinition "..(){ cd ..; }"
prop_readFunctionDefinition9 = isOk readFunctionDefinition "function foo { true; }" -- >>> prop $ isOk readFunctionDefinition "foo() (ls)"
prop_readFunctionDefinition10= isOk readFunctionDefinition "function foo () { true; }" -- >>> prop $ isOk readFunctionDefinition "function foo { true; }"
prop_readFunctionDefinition11= isWarning readFunctionDefinition "function foo{\ntrue\n}" -- >>> prop $ isOk readFunctionDefinition "function foo () { true; }"
prop_readFunctionDefinition12= isOk readFunctionDefinition "function []!() { true; }" -- >>> prop $ isWarning readFunctionDefinition "function foo{\ntrue\n}"
prop_readFunctionDefinition13= isOk readFunctionDefinition "@require(){ true; }" -- >>> prop $ isOk readFunctionDefinition "function []!() { true; }"
-- >>> prop $ isOk readFunctionDefinition "@require(){ true; }"
readFunctionDefinition = called "function" $ do readFunctionDefinition = called "function" $ do
start <- startSpan start <- startSpan
functionSignature <- try readFunctionSignature functionSignature <- try readFunctionSignature
@@ -2546,9 +2587,10 @@ readFunctionDefinition = called "function" $ do
g_Rparen g_Rparen
return () return ()
prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }" -- |
prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }" -- >>> prop $ isOk readCoProc "coproc foo { echo bar; }"
prop_readCoProc3 = isOk readCoProc "coproc echo bar" -- >>> prop $ isOk readCoProc "coproc { echo bar; }"
-- >>> prop $ isOk readCoProc "coproc echo bar"
readCoProc = called "coproc" $ do readCoProc = called "coproc" $ do
start <- startSpan start <- startSpan
try $ do try $ do
@@ -2575,7 +2617,8 @@ readCoProc = called "coproc" $ do
readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing) 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 readCompoundCommand = do
cmd <- choice [ cmd <- choice [
readBraceGroup, readBraceGroup,
@@ -2667,24 +2710,26 @@ readLiteralForParser parser = do
id <- endSpan start id <- endSpan start
return $ T_Literal id str return $ T_Literal id str
prop_readAssignmentWord = isOk readAssignmentWord "a=42" -- |
prop_readAssignmentWord2 = isOk readAssignmentWord "b=(1 2 3)" -- >>> prop $ isOk readAssignmentWord "a=42"
prop_readAssignmentWord3 = isWarning readAssignmentWord "$b = 13" -- >>> prop $ isOk readAssignmentWord "b=(1 2 3)"
prop_readAssignmentWord4 = isWarning readAssignmentWord "b = $(lol)" -- >>> prop $ isWarning readAssignmentWord "$b = 13"
prop_readAssignmentWord5 = isOk readAssignmentWord "b+=lol" -- >>> prop $ isWarning readAssignmentWord "b = $(lol)"
prop_readAssignmentWord6 = isWarning readAssignmentWord "b += (1 2 3)" -- >>> prop $ isOk readAssignmentWord "b+=lol"
prop_readAssignmentWord7 = isOk readAssignmentWord "a[3$n'']=42" -- >>> prop $ isWarning readAssignmentWord "b += (1 2 3)"
prop_readAssignmentWord8 = isOk readAssignmentWord "a[4''$(cat foo)]=42" -- >>> prop $ isOk readAssignmentWord "a[3$n'']=42"
prop_readAssignmentWord9 = isOk readAssignmentWord "IFS= " -- >>> prop $ isOk readAssignmentWord "a[4''$(cat foo)]=42"
prop_readAssignmentWord9a= isOk readAssignmentWord "foo=" -- >>> prop $ isOk readAssignmentWord "IFS= "
prop_readAssignmentWord9b= isOk readAssignmentWord "foo= " -- >>> prop $ isOk readAssignmentWord "foo="
prop_readAssignmentWord9c= isOk readAssignmentWord "foo= #bar" -- >>> prop $ isOk readAssignmentWord "foo= "
prop_readAssignmentWord10= isWarning readAssignmentWord "foo$n=42" -- >>> prop $ isOk readAssignmentWord "foo= #bar"
prop_readAssignmentWord11= isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )" -- >>> prop $ isWarning readAssignmentWord "foo$n=42"
prop_readAssignmentWord12= isOk readAssignmentWord "a[b <<= 3 + c]='thing'" -- >>> prop $ isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )"
prop_readAssignmentWord13= isOk readAssignmentWord "var=( (1 2) (3 4) )" -- >>> prop $ isOk readAssignmentWord "a[b <<= 3 + c]='thing'"
prop_readAssignmentWord14= isOk readAssignmentWord "var=( 1 [2]=(3 4) )" -- >>> prop $ isOk readAssignmentWord "var=( (1 2) (3 4) )"
prop_readAssignmentWord15= 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 readAssignmentWord = readAssignmentWordExt True
readWellFormedAssignment = readAssignmentWordExt False readWellFormedAssignment = readAssignmentWordExt False
readAssignmentWordExt lenient = try $ do readAssignmentWordExt lenient = try $ do
@@ -2714,9 +2759,10 @@ readAssignmentWordExt lenient = try $ do
when (hasLeftSpace || hasRightSpace) $ when (hasLeftSpace || hasRightSpace) $
parseNoteAt pos ErrorC 1068 $ parseNoteAt pos ErrorC 1068 $
"Don't put spaces around the " "Don't put spaces around the "
++ if op == Append ++ (if op == Append
then "+= when appending." then "+= when appending"
else "= in assignments." else "= in assignments")
++ " (or quote to make it literal)."
value <- readArray <|> readNormalWord value <- readArray <|> readNormalWord
spacing spacing
return $ T_Assignment id op variable indices value return $ T_Assignment id op variable indices value
@@ -2877,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 = ifParse p t f =
(lookAhead (try p) >> t) <|> f (lookAhead (try p) >> t) <|> f
prop_readShebang1 = isOk readShebang "#!/bin/sh\n" -- |
prop_readShebang2 = isWarning readShebang "!# /bin/sh\n" -- >>> prop $ isOk readShebang "#!/bin/sh\n"
prop_readShebang3 = isNotOk readShebang "#shellcheck shell=/bin/sh\n" -- >>> prop $ isWarning readShebang "!# /bin/sh\n"
prop_readShebang4 = isWarning readShebang "! /bin/sh" -- >>> prop $ isNotOk readShebang "#shellcheck shell=/bin/sh\n"
prop_readShebang5 = isWarning readShebang "\n#!/bin/sh" -- >>> prop $ isWarning readShebang "! /bin/sh"
prop_readShebang6 = isWarning readShebang " # Copyright \n!#/bin/bash" -- >>> prop $ isWarning readShebang "\n#!/bin/sh"
prop_readShebang7 = isNotOk readShebang "# Copyright \nfoo\n#!/bin/bash" -- >>> prop $ isWarning readShebang " # Copyright \n!#/bin/bash"
-- >>> prop $ isNotOk readShebang "# Copyright \nfoo\n#!/bin/bash"
readShebang = do readShebang = do
anyShebang <|> try readMissingBang <|> withHeader anyShebang <|> try readMissingBang <|> withHeader
many linewhitespace many linewhitespace
@@ -2966,11 +3013,12 @@ verifyEof = eof <|> choice [
try (lookAhead p) try (lookAhead p)
action action
prop_readScript1 = isOk readScriptFile "#!/bin/bash\necho hello world\n" -- |
prop_readScript2 = isWarning readScriptFile "#!/bin/bash\r\necho hello world\n" -- >>> prop $ isOk readScriptFile "#!/bin/bash\necho hello world\n"
prop_readScript3 = isWarning readScriptFile "#!/bin/bash\necho hello\xA0world" -- >>> prop $ isWarning readScriptFile "#!/bin/bash\r\necho hello world\n"
prop_readScript4 = isWarning readScriptFile "#!/usr/bin/perl\nfoo=(" -- >>> prop $ isWarning readScriptFile "#!/bin/bash\necho hello\xA0world"
prop_readScript5 = isOk readScriptFile "#!/bin/bash\n#This is an empty script\n\n" -- >>> prop $ isWarning readScriptFile "#!/usr/bin/perl\nfoo=("
-- >>> prop $ isOk readScriptFile "#!/bin/bash\n#This is an empty script\n\n"
readScriptFile = do readScriptFile = do
start <- startSpan start <- startSpan
pos <- getPosition pos <- getPosition
@@ -3293,7 +3341,3 @@ tryWithErrors parser = do
endInput <- getInput endInput <- getInput
endState <- getState endState <- getState
return (result, endPos, endInput, endState) return (result, endPos, endInput, endState)
return []
runTests = $quickCheckAll

View File

@@ -1,35 +1,3 @@
# This file was automatically generated by stack init resolver: lts-12.9
# 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
packages: 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

View File

@@ -1,78 +1,2 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# This file strips all unit tests from ShellCheck, removing # This file was deprecated by the doctest build.
# 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

View File

@@ -16,10 +16,14 @@ and is still highly experimental.
Make sure you're plugged in and have screen/tmux in place, Make sure you're plugged in and have screen/tmux in place,
then re-run with $0 --run to continue. then re-run with $0 --run to continue.
Also note that 'dist' will be deleted.
EOF EOF
exit 0 exit 0
} }
echo "Deleting 'dist'..."
rm -rf dist
log=$(mktemp) || die "Can't create temp file" log=$(mktemp) || die "Can't create temp file"
date >> "$log" || die "Can't write to log" date >> "$log" || die "Can't write to log"
@@ -63,7 +67,8 @@ opensuse:latest zypper install -y cabal-install ghc
ubuntu:18.04 apt-get update && apt-get install -y cabal-install ubuntu:18.04 apt-get update && apt-get install -y cabal-install
ubuntu:17.10 apt-get update && apt-get install -y cabal-install ubuntu:17.10 apt-get update && apt-get install -y cabal-install
# Misc # Misc Haskell including current and latest Stack build
ubuntu:18.10 set -e; apt-get update && apt-get install -y curl && curl -sSL https://get.haskellstack.org/ | sh -s - -f && cd /mnt && exec test/stacktest
haskell:latest true haskell:latest true
# Known to currently fail # Known to currently fail

12
test/doctests.hs Normal file
View File

@@ -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

View File

@@ -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

27
test/stacktest Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
# This script builds ShellCheck through `stack` using
# various resolvers. It's run via distrotest.
resolvers=(
nightly-"$(date -d "3 days ago" +"%Y-%m-%d")"
)
die() { echo "$*" >&2; exit 1; }
[ -e "ShellCheck.cabal" ] ||
die "ShellCheck.cabal not in current dir"
[ -e "stack.yaml" ] ||
die "stack.yaml not in current dir"
command -v stack ||
die "stack is missing"
stack setup || die "Failed to setup with default resolver"
stack build --test || die "Failed to build/test with default resolver"
for resolver in "${resolvers[@]}"
do
stack --resolver="$resolver" setup || die "Failed to setup $resolver"
stack --resolver="$resolver" build --test || die "Failed build/test with $resolver!"
done
echo "Success"