mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-09-30 00:39:19 +08:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
cff3e22911 | ||
|
5669eb2203 | ||
|
b68df1882d | ||
|
087865c680 | ||
|
19c6f22c3f | ||
|
98952df35b | ||
|
a277efdbb1 | ||
|
45687b0548 | ||
|
ecdc21b0b7 | ||
|
4eb42fa3c1 | ||
|
f02c297fdd | ||
|
ea83b602d7 | ||
|
88cd21fd0f | ||
|
83435c4f2e | ||
|
4324b4a213 | ||
|
a69d6cb661 | ||
|
8442695b73 | ||
|
d6bb8fc0d8 | ||
|
2e59eba6eb | ||
|
99e9d5c54b | ||
|
c5756760cb | ||
|
19355226e1 | ||
|
4e7e3f9456 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Build Lol
|
name: Build ShellCheck
|
||||||
|
|
||||||
# Run this workflow every time a new commit pushed to your repository
|
# Run this workflow every time a new commit pushed to your repository
|
||||||
on: push
|
on: push
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir source
|
mkdir source
|
||||||
cabal sdist
|
cabal sdist
|
||||||
mv dist/*.tar.gz source/source.tar.gz
|
mv dist-newstyle/sdist/*.tar.gz source/source.tar.gz
|
||||||
|
|
||||||
- name: Deduce tags
|
- name: Deduce tags
|
||||||
run: |
|
run: |
|
||||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,13 +1,14 @@
|
|||||||
## Git
|
## v0.7.2 - 2021-04-19
|
||||||
### Added
|
### Added
|
||||||
- `disable` directives can now be a range, e.g. `disable=SC3000-SC4000`
|
- `disable` directives can now be a range, e.g. `disable=SC3000-SC4000`
|
||||||
|
- SC1143: Warn about line continuations in comments
|
||||||
- SC2259/SC2260: Warn when redirections override pipes
|
- SC2259/SC2260: Warn when redirections override pipes
|
||||||
- SC2261: Warn about multiple competing redirections
|
- SC2261: Warn about multiple competing redirections
|
||||||
- SC2262/SC2263: Warn about aliases declared and used in the same parsing unit
|
- SC2262/SC2263: Warn about aliases declared and used in the same parsing unit
|
||||||
- SC2264: Warn about wrapper functions that blatantly recurse
|
- SC2264: Warn about wrapper functions that blatantly recurse
|
||||||
- SC2265/SC2266: Warn when using & or | with test statements
|
- SC2265/SC2266: Warn when using & or | with test statements
|
||||||
- SC2267: Warn when using xargs -i instead of -I
|
- SC2267: Warn when using xargs -i instead of -I
|
||||||
- Optional avoid-x-comparisons: Style warning SC2268 for `[ x$var = xval ]`
|
- SC2268: Warn about unnecessary x-comparisons like `[ x$var = xval ]`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- SC1072/SC1073 now respond to disable annotations, though ignoring parse errors
|
- SC1072/SC1073 now respond to disable annotations, though ignoring parse errors
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
- POSIX/dash unsupported feature warnings now have individual SC3xxx codes
|
- POSIX/dash unsupported feature warnings now have individual SC3xxx codes
|
||||||
- SC1090: A leading `$x/` or `$(x)/` is now treated as `./` when locating files
|
- SC1090: A leading `$x/` or `$(x)/` is now treated as `./` when locating files
|
||||||
- SC2154: Variables appearing in -z/-n tests are no longer considered unassigned
|
- SC2154: Variables appearing in -z/-n tests are no longer considered unassigned
|
||||||
- SC2270-SC2285: Improved warnings about misused =, e.g. `${var}=42`
|
- SC2270-SC2285: Improved warnings about misused `=`, e.g. `${var}=42`
|
||||||
|
|
||||||
|
|
||||||
## v0.7.1 - 2020-04-04
|
## v0.7.1 - 2020-04-04
|
||||||
@@ -164,7 +165,7 @@
|
|||||||
- SC2204/SC2205: Warn about `( -z foo )` and `( foo -eq bar )`
|
- SC2204/SC2205: Warn about `( -z foo )` and `( foo -eq bar )`
|
||||||
- SC2200/SC2201: Warn about brace expansion in [/[[
|
- SC2200/SC2201: Warn about brace expansion in [/[[
|
||||||
- SC2198/SC2199: Warn about arrays in [/[[
|
- SC2198/SC2199: Warn about arrays in [/[[
|
||||||
- SC2196/SC2197: Warn about deprected egrep/fgrep
|
- SC2196/SC2197: Warn about deprecated egrep/fgrep
|
||||||
- SC2195: Warn about unmatchable case branches
|
- SC2195: Warn about unmatchable case branches
|
||||||
- SC2194: Warn about constant 'case' statements
|
- SC2194: Warn about constant 'case' statements
|
||||||
- SC2193: Warn about `[[ file.png == *.mp3 ]]` and other unmatchables
|
- SC2193: Warn about `[[ file.png == *.mp3 ]]` and other unmatchables
|
||||||
@@ -181,7 +182,7 @@
|
|||||||
### Fixed
|
### Fixed
|
||||||
- `-c` no longer suggested when using `grep -o | wc`
|
- `-c` no longer suggested when using `grep -o | wc`
|
||||||
- Comments and whitespace are now allowed before filewide directives
|
- Comments and whitespace are now allowed before filewide directives
|
||||||
- Here doc delimters with esoteric quoting like `foo""` are now handled
|
- Here doc delimiters with esoteric quoting like `foo""` are now handled
|
||||||
- SC2095 about `ssh` in while read loops is now suppressed when using `-n`
|
- SC2095 about `ssh` in while read loops is now suppressed when using `-n`
|
||||||
- `%(%Y%M%D)T` now recognized as a single formatter in `printf` checks
|
- `%(%Y%M%D)T` now recognized as a single formatter in `printf` checks
|
||||||
- `grep -F` now suppresses regex related suggestions
|
- `grep -F` now suppresses regex related suggestions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
Name: ShellCheck
|
Name: ShellCheck
|
||||||
Version: 0.7.1
|
Version: 0.7.2
|
||||||
Synopsis: Shell script analysis tool
|
Synopsis: Shell script analysis tool
|
||||||
License: GPL-3
|
License: GPL-3
|
||||||
License-file: LICENSE
|
License-file: LICENSE
|
||||||
|
@@ -22,7 +22,7 @@ RUN curl -L "https://downloads.haskell.org/~cabal/cabal-install-3.2.0.0/cabal-in
|
|||||||
ENV CABALOPTS "--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg"
|
ENV CABALOPTS "--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg"
|
||||||
|
|
||||||
# Prebuild the dependencies
|
# Prebuild the dependencies
|
||||||
RUN cabal update && IFS=';' && cabal install $CABALOPTS --lib Diff-0.4.0 base-compat-0.11.2 base-orphans-0.8.4 dlist-1.0 hashable-1.3.0.0 indexed-traversable-0.1.1 integer-logarithms-1.0.3.1 primitive-0.7.1.0 regex-base-0.94.0.0 splitmix-0.1.0.3 tagged-0.8.6.1 th-abstraction-0.4.2.0 transformers-compat-0.6.6 base-compat-batteries-0.11.2 time-compat-1.9.5 unordered-containers-0.2.13.0 data-fix-0.3.1 vector-0.12.2.0 scientific-0.3.6.2 regex-tdfa-1.3.1.0 random-1.2.0 distributive-0.6.2.1 attoparsec-0.13.2.5 uuid-types-1.0.3 comonad-5.0.8 bifunctors-5.5.10 assoc-1.0.2 these-1.1.1.1 strict-0.4.0.1 aeson-1.5.5.1
|
RUN cabal update && IFS=';' && cabal install --dependencies-only $CABALOPTS ShellCheck
|
||||||
|
|
||||||
# Copy the build script
|
# Copy the build script
|
||||||
COPY build /usr/bin
|
COPY build /usr/bin
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
set -xe
|
set -xe
|
||||||
{
|
{
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
cabal update
|
||||||
( IFS=';'; cabal build $CABALOPTS )
|
( IFS=';'; cabal build $CABALOPTS )
|
||||||
|
@@ -21,7 +21,7 @@ RUN curl -L "https://downloads.haskell.org/~cabal/cabal-install-3.2.0.0/cabal-in
|
|||||||
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections;--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg"
|
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections;--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg"
|
||||||
|
|
||||||
# Prebuild the dependencies
|
# Prebuild the dependencies
|
||||||
RUN cabal update && IFS=';' && cabal install $CABALOPTS --lib Diff-0.4.0 base-compat-0.11.2 base-orphans-0.8.4 dlist-1.0 hashable-1.3.0.0 indexed-traversable-0.1.1 integer-logarithms-1.0.3.1 primitive-0.7.1.0 regex-base-0.94.0.0 splitmix-0.1.0.3 tagged-0.8.6.1 th-abstraction-0.4.2.0 transformers-compat-0.6.6 base-compat-batteries-0.11.2 time-compat-1.9.5 unordered-containers-0.2.13.0 data-fix-0.3.1 vector-0.12.2.0 scientific-0.3.6.2 regex-tdfa-1.3.1.0 random-1.2.0 distributive-0.6.2.1 attoparsec-0.13.2.5 uuid-types-1.0.3 comonad-5.0.8 bifunctors-5.5.10 assoc-1.0.2 these-1.1.1.1 strict-0.4.0.1 aeson-1.5.5.1
|
RUN cabal update && IFS=';' && cabal install --dependencies-only $CABALOPTS ShellCheck
|
||||||
|
|
||||||
# Copy the build script
|
# Copy the build script
|
||||||
COPY build /usr/bin
|
COPY build /usr/bin
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
set -xe
|
set -xe
|
||||||
{
|
{
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
cabal update
|
||||||
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
||||||
|
@@ -51,7 +51,7 @@ RUN pirun apt-get install -y ghc cabal-install
|
|||||||
# Finally we can build the current dependencies. This takes hours.
|
# Finally we can build the current dependencies. This takes hours.
|
||||||
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections;--gcc-options;-Os -Wl,--gc-sections -ffunction-sections -fdata-sections"
|
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections;--gcc-options;-Os -Wl,--gc-sections -ffunction-sections -fdata-sections"
|
||||||
RUN pirun cabal update
|
RUN pirun cabal update
|
||||||
RUN IFS=";" && pirun cabal install --lib $CABALOPTS Diff-0.4.0 base-compat-0.11.2 base-orphans-0.8.4 dlist-1.0 hashable-1.3.1.0 indexed-traversable-0.1.1 integer-logarithms-1.0.3.1 primitive-0.7.1.0 regex-base-0.94.0.1 splitmix-0.1.0.3 tagged-0.8.6.1 th-abstraction-0.4.2.0 transformers-compat-0.6.6 base-compat-batteries-0.11.2 time-compat-1.9.5 unordered-containers-0.2.13.0 data-fix-0.3.1 vector-0.12.2.0 scientific-0.3.6.2 regex-tdfa-1.3.1.0 random-1.2.0 distributive-0.6.2.1 attoparsec-0.13.2.5 uuid-types-1.0.4 comonad-5.0.8 bifunctors-5.5.10 assoc-1.0.2 these-1.1.1.1 strict-0.4.0.1 aeson-1.5.6.0
|
RUN IFS=";" && pirun cabal install --dependencies-only $CABALOPTS ShellCheck
|
||||||
|
|
||||||
# Copy the build script
|
# Copy the build script
|
||||||
WORKDIR /pi/scratch
|
WORKDIR /pi/scratch
|
||||||
|
@@ -3,7 +3,7 @@ set -xe
|
|||||||
cd /scratch
|
cd /scratch
|
||||||
{
|
{
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
# This script does not cabal update because compiling anything new is slow
|
# This script does not cabal update because compiling anything new is slow
|
||||||
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
||||||
|
@@ -1,30 +0,0 @@
|
|||||||
FROM ubuntu:20.04
|
|
||||||
|
|
||||||
ENV TARGET powerpc64le-linux-gnu
|
|
||||||
ENV TARGETNAME linux.ppc64le
|
|
||||||
|
|
||||||
# Build dependencies
|
|
||||||
USER root
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update && apt-get install -y ghc automake autoconf build-essential llvm curl qemu-user-static gcc-$TARGET
|
|
||||||
|
|
||||||
# Build GHC
|
|
||||||
WORKDIR /ghc
|
|
||||||
RUN curl -L "https://downloads.haskell.org/~ghc/8.10.4/ghc-8.10.4-src.tar.xz" | tar xJ --strip-components=1
|
|
||||||
RUN ./boot && ./configure --host x86_64-linux-gnu --build x86_64-linux-gnu --target "$TARGET" --enable-unregisterised
|
|
||||||
RUN cp mk/flavours/quick-cross.mk mk/build.mk && make -j "$(nproc)"
|
|
||||||
RUN make install
|
|
||||||
RUN curl -L "https://downloads.haskell.org/~cabal/cabal-install-3.2.0.0/cabal-install-3.2.0.0-x86_64-unknown-linux.tar.xz" | tar xJv -C /usr/local/bin
|
|
||||||
|
|
||||||
# Due to an apparent cabal bug, we specify our options directly to cabal
|
|
||||||
# It won't reuse caches if ghc-options are specified in ~/.cabal/config
|
|
||||||
ENV CABALOPTS "--ghc-options;-optl-Wl,-fuse-ld=bfd -split-sections -optc-Os -optc-Wl,--gc-sections;--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg"
|
|
||||||
|
|
||||||
# Prebuild the dependencies
|
|
||||||
#RUN cabal update && IFS=';' && cabal install $CABALOPTS --lib Diff-0.4.0 base-compat-0.11.2 base-orphans-0.8.4 dlist-1.0 hashable-1.3.0.0 indexed-traversable-0.1.1 integer-logarithms-1.0.3.1 primitive-0.7.1.0 regex-base-0.94.0.0 splitmix-0.1.0.3 tagged-0.8.6.1 th-abstraction-0.4.2.0 transformers-compat-0.6.6 base-compat-batteries-0.11.2 time-compat-1.9.5 unordered-containers-0.2.13.0 data-fix-0.3.1 vector-0.12.2.0 scientific-0.3.6.2 regex-tdfa-1.3.1.0 random-1.2.0 distributive-0.6.2.1 attoparsec-0.13.2.5 uuid-types-1.0.3 comonad-5.0.8 bifunctors-5.5.10 assoc-1.0.2 these-1.1.1.1 strict-0.4.0.1 aeson-1.5.5.1
|
|
||||||
|
|
||||||
# Copy the build script
|
|
||||||
COPY build /usr/bin
|
|
||||||
|
|
||||||
WORKDIR /scratch
|
|
||||||
ENTRYPOINT ["/usr/bin/build"]
|
|
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -xe
|
|
||||||
{
|
|
||||||
tar xzv --strip-components=1
|
|
||||||
./striptests
|
|
||||||
mkdir "$TARGETNAME"
|
|
||||||
cabal update
|
|
||||||
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
|
||||||
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
|
||||||
ls -l "$TARGETNAME"
|
|
||||||
"$TARGET-strip" -s "$TARGETNAME/shellcheck"
|
|
||||||
ls -l "$TARGETNAME"
|
|
||||||
qemu-ppc64le-static "$TARGETNAME/shellcheck" --version
|
|
||||||
} >&2
|
|
||||||
tar czv "$TARGETNAME"
|
|
@@ -1 +0,0 @@
|
|||||||
koalaman/scbuilder-linux-ppc64le
|
|
@@ -2,7 +2,7 @@
|
|||||||
set -xe
|
set -xe
|
||||||
{
|
{
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
cabal update
|
||||||
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
||||||
|
@@ -5,7 +5,7 @@ ENV TARGETNAME windows.x86_64
|
|||||||
# We don't need wine32, even though it complains
|
# We don't need wine32, even though it complains
|
||||||
USER root
|
USER root
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
RUN apt-get update && apt-get install -y curl busybox wine
|
RUN apt-get update && apt-get install -y curl busybox wine winbind
|
||||||
|
|
||||||
# Fetch Windows version, will be available under z:\haskell
|
# Fetch Windows version, will be available under z:\haskell
|
||||||
WORKDIR /haskell
|
WORKDIR /haskell
|
||||||
@@ -19,8 +19,8 @@ ENV WINEPATH /haskell/bin
|
|||||||
# that necessitated this but I don't care enough to find out
|
# that necessitated this but I don't care enough to find out
|
||||||
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections"
|
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections"
|
||||||
|
|
||||||
# Precompile some deps to speed up later builds. This list is just copied from `cabal build`
|
# Precompile some deps to speed up later builds
|
||||||
RUN wine /haskell/bin/cabal.exe update && IFS=';' && wine /haskell/bin/cabal.exe install $CABALOPTS --lib Diff-0.4.0 base-compat-0.11.2 base-orphans-0.8.4 dlist-1.0 hashable-1.3.0.0 indexed-traversable-0.1.1 integer-logarithms-1.0.3.1 primitive-0.7.1.0 regex-base-0.94.0.0 splitmix-0.1.0.3 tagged-0.8.6.1 th-abstraction-0.4.2.0 transformers-compat-0.6.6 base-compat-batteries-0.11.2 time-compat-1.9.5 unordered-containers-0.2.13.0 data-fix-0.3.1 vector-0.12.2.0 scientific-0.3.6.2 regex-tdfa-1.3.1.0 random-1.2.0 distributive-0.6.2.1 attoparsec-0.13.2.5 uuid-types-1.0.3 comonad-5.0.8 bifunctors-5.5.10 assoc-1.0.2 these-1.1.1.1 strict-0.4.0.1 aeson-1.5.5.1
|
RUN wine /haskell/bin/cabal.exe update && IFS=';' && wine /haskell/bin/cabal.exe install --lib --dependencies-only $CABALOPTS ShellCheck
|
||||||
|
|
||||||
COPY build /usr/bin
|
COPY build /usr/bin
|
||||||
WORKDIR /scratch
|
WORKDIR /scratch
|
||||||
|
@@ -6,7 +6,7 @@ cabal() {
|
|||||||
set -xe
|
set -xe
|
||||||
{
|
{
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
cabal update
|
||||||
( IFS=';'; cabal build $CABALOPTS )
|
( IFS=';'; cabal build $CABALOPTS )
|
||||||
|
@@ -17,9 +17,11 @@
|
|||||||
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.ASTLib where
|
module ShellCheck.ASTLib where
|
||||||
|
|
||||||
import ShellCheck.AST
|
import ShellCheck.AST
|
||||||
|
import ShellCheck.Regex
|
||||||
|
|
||||||
import Control.Monad.Writer
|
import Control.Monad.Writer
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
@@ -31,6 +33,8 @@ import Data.Maybe
|
|||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import Numeric (showHex)
|
import Numeric (showHex)
|
||||||
|
|
||||||
|
import Test.QuickCheck
|
||||||
|
|
||||||
arguments (T_SimpleCommand _ _ (cmd:args)) = args
|
arguments (T_SimpleCommand _ _ (cmd:args)) = args
|
||||||
|
|
||||||
-- Is this a type of loop?
|
-- Is this a type of loop?
|
||||||
@@ -672,3 +676,43 @@ isAnnotationIgnoringCode code t =
|
|||||||
where
|
where
|
||||||
hasNum (DisableComment from to) = code >= from && code < to
|
hasNum (DisableComment from to) = code >= from && code < to
|
||||||
hasNum _ = False
|
hasNum _ = False
|
||||||
|
|
||||||
|
prop_executableFromShebang1 = executableFromShebang "/bin/sh" == "sh"
|
||||||
|
prop_executableFromShebang2 = executableFromShebang "/bin/bash" == "bash"
|
||||||
|
prop_executableFromShebang3 = executableFromShebang "/usr/bin/env ksh" == "ksh"
|
||||||
|
prop_executableFromShebang4 = executableFromShebang "/usr/bin/env -S foo=bar bash -x" == "bash"
|
||||||
|
prop_executableFromShebang5 = executableFromShebang "/usr/bin/env --split-string=bash -x" == "bash"
|
||||||
|
prop_executableFromShebang6 = executableFromShebang "/usr/bin/env --split-string=foo=bar bash -x" == "bash"
|
||||||
|
prop_executableFromShebang7 = executableFromShebang "/usr/bin/env --split-string bash -x" == "bash"
|
||||||
|
prop_executableFromShebang8 = executableFromShebang "/usr/bin/env --split-string foo=bar bash -x" == "bash"
|
||||||
|
prop_executableFromShebang9 = executableFromShebang "/usr/bin/env foo=bar dash" == "dash"
|
||||||
|
prop_executableFromShebang10 = executableFromShebang "/bin/busybox sh" == "ash"
|
||||||
|
prop_executableFromShebang11 = executableFromShebang "/bin/busybox ash" == "ash"
|
||||||
|
|
||||||
|
-- Get the shell executable from a string like '/usr/bin/env bash'
|
||||||
|
executableFromShebang :: String -> String
|
||||||
|
executableFromShebang = shellFor
|
||||||
|
where
|
||||||
|
re = mkRegex "/env +(-S|--split-string=?)? *(.*)"
|
||||||
|
shellFor s | s `matches` re =
|
||||||
|
case matchRegex re s of
|
||||||
|
Just [flag, shell] -> fromEnvArgs (words shell)
|
||||||
|
_ -> ""
|
||||||
|
shellFor sb =
|
||||||
|
case words sb of
|
||||||
|
[] -> ""
|
||||||
|
[x] -> basename x
|
||||||
|
(first:second:args) | basename first == "busybox" ->
|
||||||
|
case basename second of
|
||||||
|
"sh" -> "ash" -- busybox sh is ash
|
||||||
|
x -> x
|
||||||
|
(first:args) | basename first == "env" ->
|
||||||
|
fromEnvArgs args
|
||||||
|
(first:_) -> basename first
|
||||||
|
|
||||||
|
fromEnvArgs args = fromMaybe "" $ find (notElem '=') $ skipFlags args
|
||||||
|
basename s = reverse . takeWhile (/= '/') . reverse $ s
|
||||||
|
skipFlags = dropWhile ("-" `isPrefixOf`)
|
||||||
|
|
||||||
|
return []
|
||||||
|
runTests = $quickCheckAll
|
||||||
|
@@ -196,6 +196,7 @@ nodeChecks = [
|
|||||||
,checkAssignToSelf
|
,checkAssignToSelf
|
||||||
,checkEqualsInCommand
|
,checkEqualsInCommand
|
||||||
,checkSecondArgIsComparison
|
,checkSecondArgIsComparison
|
||||||
|
,checkComparisonWithLeadingX
|
||||||
]
|
]
|
||||||
|
|
||||||
optionalChecks = map fst optionalTreeChecks
|
optionalChecks = map fst optionalTreeChecks
|
||||||
@@ -243,13 +244,6 @@ optionalTreeChecks = [
|
|||||||
cdPositive = "echo $VAR",
|
cdPositive = "echo $VAR",
|
||||||
cdNegative = "VAR=hello; echo $VAR"
|
cdNegative = "VAR=hello; echo $VAR"
|
||||||
}, checkUnassignedReferences' True)
|
}, checkUnassignedReferences' True)
|
||||||
|
|
||||||
,(newCheckDescription {
|
|
||||||
cdName = "avoid-x-comparisons",
|
|
||||||
cdDescription = "Warn about 'x'-prefix in comparisons",
|
|
||||||
cdPositive = "[ \"x$var\" = xval ]",
|
|
||||||
cdNegative = "[ \"$var\" = val ]"
|
|
||||||
}, nodeChecksToTreeCheck [checkComparisonWithLeadingX])
|
|
||||||
]
|
]
|
||||||
|
|
||||||
optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment])
|
optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment])
|
||||||
@@ -590,6 +584,12 @@ prop_checkShebang9 = verifyNotTree checkShebang "# shellcheck shell=sh\ntrue"
|
|||||||
prop_checkShebang10= verifyNotTree checkShebang "#!foo\n# shellcheck shell=sh ignore=SC2239\ntrue"
|
prop_checkShebang10= verifyNotTree checkShebang "#!foo\n# shellcheck shell=sh ignore=SC2239\ntrue"
|
||||||
prop_checkShebang11= verifyTree checkShebang "#!/bin/sh/\ntrue"
|
prop_checkShebang11= verifyTree checkShebang "#!/bin/sh/\ntrue"
|
||||||
prop_checkShebang12= verifyTree checkShebang "#!/bin/sh/ -xe\ntrue"
|
prop_checkShebang12= verifyTree checkShebang "#!/bin/sh/ -xe\ntrue"
|
||||||
|
prop_checkShebang13= verifyTree checkShebang "#!/bin/busybox sh"
|
||||||
|
prop_checkShebang14= verifyNotTree checkShebang "#!/bin/busybox sh\n# shellcheck shell=sh\n"
|
||||||
|
prop_checkShebang15= verifyNotTree checkShebang "#!/bin/busybox sh\n# shellcheck shell=dash\n"
|
||||||
|
prop_checkShebang16= verifyTree checkShebang "#!/bin/busybox ash"
|
||||||
|
prop_checkShebang17= verifyNotTree checkShebang "#!/bin/busybox ash\n# shellcheck shell=dash\n"
|
||||||
|
prop_checkShebang18= verifyNotTree checkShebang "#!/bin/busybox ash\n# shellcheck shell=sh\n"
|
||||||
checkShebang params (T_Annotation _ list t) =
|
checkShebang params (T_Annotation _ list t) =
|
||||||
if any isOverride list then [] else checkShebang params t
|
if any isOverride list then [] else checkShebang params t
|
||||||
where
|
where
|
||||||
@@ -1015,6 +1015,7 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) =
|
|||||||
,"alias"
|
,"alias"
|
||||||
,"sudo" -- covering "sudo sh" and such
|
,"sudo" -- covering "sudo sh" and such
|
||||||
,"docker" -- like above
|
,"docker" -- like above
|
||||||
|
,"podman"
|
||||||
,"dpkg-query"
|
,"dpkg-query"
|
||||||
,"jq" -- could also check that user provides --arg
|
,"jq" -- could also check that user provides --arg
|
||||||
,"rename"
|
,"rename"
|
||||||
@@ -1216,8 +1217,8 @@ checkQuotedCondRegex _ (TC_Binary _ _ "=~" _ rhs) =
|
|||||||
where
|
where
|
||||||
error t =
|
error t =
|
||||||
unless (isConstantNonRe t) $
|
unless (isConstantNonRe t) $
|
||||||
err (getId t) 2076
|
warn (getId t) 2076
|
||||||
"Don't quote right-hand side of =~, it'll match literally rather than as a regex."
|
"Remove quotes from right-hand side of =~ to match as a regex rather than literally."
|
||||||
re = mkRegex "[][*.+()|]"
|
re = mkRegex "[][*.+()|]"
|
||||||
hasMetachars s = s `matches` re
|
hasMetachars s = s `matches` re
|
||||||
isConstantNonRe t = fromMaybe False $ do
|
isConstantNonRe t = fromMaybe False $ do
|
||||||
@@ -3999,7 +4000,7 @@ checkComparisonWithLeadingX params t =
|
|||||||
check lhs rhs
|
check lhs rhs
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
where
|
where
|
||||||
msg = "Avoid outdated x-prefix in comparisons as it no longer serves a purpose."
|
msg = "Avoid x-prefix in comparisons as it no longer serves a purpose."
|
||||||
check lhs rhs = sequence_ $ do
|
check lhs rhs = sequence_ $ do
|
||||||
l <- fixLeadingX lhs
|
l <- fixLeadingX lhs
|
||||||
r <- fixLeadingX rhs
|
r <- fixLeadingX rhs
|
||||||
|
@@ -240,6 +240,8 @@ prop_determineShell7 = determineShellTest "#! /bin/ash" == Dash
|
|||||||
prop_determineShell8 = determineShellTest' (Just Ksh) "#!/bin/sh" == Sh
|
prop_determineShell8 = determineShellTest' (Just Ksh) "#!/bin/sh" == Sh
|
||||||
prop_determineShell9 = determineShellTest "#!/bin/env -S dash -x" == Dash
|
prop_determineShell9 = determineShellTest "#!/bin/env -S dash -x" == Dash
|
||||||
prop_determineShell10 = determineShellTest "#!/bin/env --split-string= dash -x" == Dash
|
prop_determineShell10 = determineShellTest "#!/bin/env --split-string= dash -x" == Dash
|
||||||
|
prop_determineShell11 = determineShellTest "#!/bin/busybox sh" == Dash -- busybox sh is a specific shell, not posix sh
|
||||||
|
prop_determineShell12 = determineShellTest "#!/bin/busybox ash" == Dash
|
||||||
|
|
||||||
determineShellTest = determineShellTest' Nothing
|
determineShellTest = determineShellTest' Nothing
|
||||||
determineShellTest' fallbackShell = determineShell fallbackShell . fromJust . prRoot . pScript
|
determineShellTest' fallbackShell = determineShell fallbackShell . fromJust . prRoot . pScript
|
||||||
@@ -253,19 +255,6 @@ determineShell fallbackShell t = fromMaybe Bash $
|
|||||||
headOrDefault (fromShebang s) [s | ShellOverride s <- annotations]
|
headOrDefault (fromShebang s) [s | ShellOverride s <- annotations]
|
||||||
fromShebang (T_Script _ (T_Literal _ s) _) = executableFromShebang s
|
fromShebang (T_Script _ (T_Literal _ s) _) = executableFromShebang s
|
||||||
|
|
||||||
-- Given a string like "/bin/bash" or "/usr/bin/env dash",
|
|
||||||
-- return the shell basename like "bash" or "dash"
|
|
||||||
executableFromShebang :: String -> String
|
|
||||||
executableFromShebang = shellFor
|
|
||||||
where
|
|
||||||
shellFor s | "/env " `isInfixOf` s = case matchRegex re s of
|
|
||||||
Just [flag, shell] -> shell
|
|
||||||
_ -> ""
|
|
||||||
shellFor s | ' ' `elem` s = shellFor $ takeWhile (/= ' ') s
|
|
||||||
shellFor s = reverse . takeWhile (/= '/') . reverse $ s
|
|
||||||
re = mkRegex "/env +(-S|--split-string=?)? *([^ ]*)"
|
|
||||||
|
|
||||||
|
|
||||||
-- Given a root node, make a map from Id to parent Token.
|
-- Given a root node, make a map from Id to parent Token.
|
||||||
-- This is used to populate parentMap in Parameters
|
-- This is used to populate parentMap in Parameters
|
||||||
getParentTree :: Token -> Map.Map Id Token
|
getParentTree :: Token -> Map.Map Id Token
|
||||||
|
@@ -1056,7 +1056,7 @@ checkFindRedirections = CommandCheck (Basename "find") f
|
|||||||
|
|
||||||
prop_checkWhich = verify checkWhich "which '.+'"
|
prop_checkWhich = 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_checkSudoRedirect1 = verify checkSudoRedirect "sudo echo 3 > /proc/file"
|
||||||
prop_checkSudoRedirect2 = verify checkSudoRedirect "sudo cmd < input"
|
prop_checkSudoRedirect2 = verify checkSudoRedirect "sudo cmd < input"
|
||||||
|
@@ -73,15 +73,15 @@ import qualified Data.Map as Map
|
|||||||
|
|
||||||
|
|
||||||
data SystemInterface m = SystemInterface {
|
data SystemInterface m = SystemInterface {
|
||||||
-- Read a file by filename, or return an error
|
-- | Read a file by filename, or return an error
|
||||||
siReadFile :: String -> m (Either ErrorMessage String),
|
siReadFile :: String -> m (Either ErrorMessage String),
|
||||||
-- Given:
|
-- | Given:
|
||||||
-- the current script,
|
-- the current script,
|
||||||
-- a list of source-path annotations in effect,
|
-- a list of source-path annotations in effect,
|
||||||
-- and a sourced file,
|
-- and a sourced file,
|
||||||
-- find the sourced file
|
-- find the sourced file
|
||||||
siFindSource :: String -> [String] -> String -> m FilePath,
|
siFindSource :: String -> [String] -> String -> m FilePath,
|
||||||
-- Get the configuration file (name, contents) for a filename
|
-- | Get the configuration file (name, contents) for a filename
|
||||||
siGetConfig :: String -> m (Maybe (FilePath, String))
|
siGetConfig :: String -> m (Maybe (FilePath, String))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
module ShellCheck.Parser (parseScript, runTests) where
|
module ShellCheck.Parser (parseScript, runTests) where
|
||||||
|
|
||||||
import ShellCheck.AST
|
import ShellCheck.AST
|
||||||
import ShellCheck.ASTLib
|
import ShellCheck.ASTLib hiding (runTests)
|
||||||
import ShellCheck.Data
|
import ShellCheck.Data
|
||||||
import ShellCheck.Interface
|
import ShellCheck.Interface
|
||||||
|
|
||||||
@@ -87,11 +87,23 @@ 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_spacing1 = isOk spacing " \\\n # Comment"
|
||||||
|
prop_spacing2 = isOk spacing "# We can continue lines with \\"
|
||||||
|
prop_spacing3 = isWarning spacing " \\\n # --verbose=true \\"
|
||||||
spacing = do
|
spacing = do
|
||||||
x <- many (many1 linewhitespace <|> try (string "\\\n" >> return ""))
|
x <- many (many1 linewhitespace <|> continuation)
|
||||||
optional readComment
|
optional readComment
|
||||||
return $ concat x
|
return $ concat x
|
||||||
|
where
|
||||||
|
continuation = do
|
||||||
|
try (string "\\\n")
|
||||||
|
-- The line was continued. Warn if this next line is a comment with a trailing \
|
||||||
|
whitespace <- many linewhitespace
|
||||||
|
optional $ do
|
||||||
|
x <- readComment
|
||||||
|
when ("\\" `isSuffixOf` x) $
|
||||||
|
parseProblem ErrorC 1143 "This backslash is part of a comment and does not continue the line."
|
||||||
|
return whitespace
|
||||||
|
|
||||||
spacing1 = do
|
spacing1 = do
|
||||||
spacing <- spacing
|
spacing <- spacing
|
||||||
@@ -1039,6 +1051,7 @@ readComment = do
|
|||||||
unexpecting "shellcheck annotation" readAnnotationPrefix
|
unexpecting "shellcheck annotation" readAnnotationPrefix
|
||||||
readAnyComment
|
readAnyComment
|
||||||
|
|
||||||
|
prop_readAnyComment = isOk readAnyComment "# Comment"
|
||||||
readAnyComment = do
|
readAnyComment = do
|
||||||
char '#'
|
char '#'
|
||||||
many $ noneOf "\r\n"
|
many $ noneOf "\r\n"
|
||||||
@@ -1404,6 +1417,8 @@ readNormalEscaped = called "escaped char" $ do
|
|||||||
do
|
do
|
||||||
next <- quotable <|> oneOf "?*@!+[]{}.,~#"
|
next <- quotable <|> oneOf "?*@!+[]{}.,~#"
|
||||||
when (next == ' ') $ checkTrailingSpaces pos <|> return ()
|
when (next == ' ') $ checkTrailingSpaces pos <|> return ()
|
||||||
|
-- Check if this line is followed by a commented line with a trailing backslash
|
||||||
|
when (next == '\n') $ try . lookAhead $ void spacing
|
||||||
return $ if next == '\n' then "" else [next]
|
return $ if next == '\n' then "" else [next]
|
||||||
<|>
|
<|>
|
||||||
do
|
do
|
||||||
@@ -3216,8 +3231,8 @@ readScriptFile sourced = do
|
|||||||
let ignoreShebang = shellAnnotationSpecified || shellFlagSpecified
|
let ignoreShebang = shellAnnotationSpecified || shellFlagSpecified
|
||||||
|
|
||||||
unless ignoreShebang $
|
unless ignoreShebang $
|
||||||
verifyShebang pos (getShell shebangString)
|
verifyShebang pos (executableFromShebang shebangString)
|
||||||
if ignoreShebang || isValidShell (getShell shebangString) /= Just False
|
if ignoreShebang || isValidShell (executableFromShebang shebangString) /= Just False
|
||||||
then do
|
then do
|
||||||
commands <- withAnnotations annotations readCompoundListOrEmpty
|
commands <- withAnnotations annotations readCompoundListOrEmpty
|
||||||
id <- endSpan start
|
id <- endSpan start
|
||||||
@@ -3231,17 +3246,6 @@ readScriptFile sourced = do
|
|||||||
return $ T_Script id shebang []
|
return $ T_Script id shebang []
|
||||||
|
|
||||||
where
|
where
|
||||||
basename s = reverse . takeWhile (/= '/') . reverse $ s
|
|
||||||
skipFlags = dropWhile ("-" `isPrefixOf`)
|
|
||||||
getShell sb =
|
|
||||||
case words sb of
|
|
||||||
[] -> ""
|
|
||||||
[x] -> basename x
|
|
||||||
(first:args) ->
|
|
||||||
if basename first == "env"
|
|
||||||
then fromMaybe "" $ find (notElem '=') $ skipFlags args
|
|
||||||
else basename first
|
|
||||||
|
|
||||||
verifyShebang pos s = do
|
verifyShebang pos s = do
|
||||||
case isValidShell s of
|
case isValidShell s of
|
||||||
Just True -> return ()
|
Just True -> return ()
|
||||||
|
@@ -4,6 +4,7 @@ import Control.Monad
|
|||||||
import System.Exit
|
import System.Exit
|
||||||
import qualified ShellCheck.Analytics
|
import qualified ShellCheck.Analytics
|
||||||
import qualified ShellCheck.AnalyzerLib
|
import qualified ShellCheck.AnalyzerLib
|
||||||
|
import qualified ShellCheck.ASTLib
|
||||||
import qualified ShellCheck.Checker
|
import qualified ShellCheck.Checker
|
||||||
import qualified ShellCheck.Checks.Commands
|
import qualified ShellCheck.Checks.Commands
|
||||||
import qualified ShellCheck.Checks.Custom
|
import qualified ShellCheck.Checks.Custom
|
||||||
@@ -17,6 +18,7 @@ main = do
|
|||||||
results <- sequence [
|
results <- sequence [
|
||||||
ShellCheck.Analytics.runTests
|
ShellCheck.Analytics.runTests
|
||||||
,ShellCheck.AnalyzerLib.runTests
|
,ShellCheck.AnalyzerLib.runTests
|
||||||
|
,ShellCheck.ASTLib.runTests
|
||||||
,ShellCheck.Checker.runTests
|
,ShellCheck.Checker.runTests
|
||||||
,ShellCheck.Checks.Commands.runTests
|
,ShellCheck.Checks.Commands.runTests
|
||||||
,ShellCheck.Checks.Custom.runTests
|
,ShellCheck.Checks.Custom.runTests
|
||||||
|
Reference in New Issue
Block a user