mirror of
https://github.com/koalaman/shellcheck
synced 2025-07-06 04:51:37 -07:00
Compare commits
99 commits
Author | SHA1 | Date | |
---|---|---|---|
|
20d11c1c33 | ||
|
47d358c1d4 | ||
|
ad58768563 | ||
|
62a8ecf9bf | ||
|
0b5410d759 | ||
|
975cfeee50 | ||
|
b381658dbc | ||
|
950578ae0e | ||
|
f78714e0f6 | ||
|
de07ec1c56 | ||
|
85066dd805 | ||
|
140274b810 | ||
|
dc41f0cc5b | ||
|
fbb8386797 | ||
|
efb5a5a274 | ||
|
553a80f77a | ||
|
7fc992d0dc | ||
|
c553288085 | ||
|
1be41dd652 | ||
|
2eddec86d3 | ||
|
c41f3a4b8a | ||
|
574c6d18fb | ||
|
e4853af5b0 | ||
|
72af76f443 | ||
|
8ff0c5be7a | ||
|
4f628cbe2a | ||
|
bc60607f9e | ||
|
3a9ddae06b | ||
|
cbf0b33463 | ||
|
fe315a25c4 | ||
|
d3001f337a | ||
|
7deb7e853b | ||
|
26b949b9b0 | ||
|
5adfea21ee | ||
|
0ecaf2b5f1 | ||
|
195b70db8c | ||
|
3c75d82db5 | ||
|
7f3f014d49 | ||
|
944d87915a | ||
|
47bff1d5fd | ||
|
0ee46a0f33 | ||
|
792466bc22 | ||
|
097018754b | ||
|
f2932ebcdc | ||
|
5e3e98bcb0 | ||
|
68bc17b8ea | ||
|
5c2be767ab | ||
|
79e43c4550 | ||
|
ca65071d77 | ||
|
8a1b24c7af | ||
|
88e441453b | ||
|
1487e57a46 | ||
|
68e6f02267 | ||
|
c7611dfcc6 | ||
|
15f132e167 | ||
|
4e69767b03 | ||
|
8bf8cf5cc7 | ||
|
17ebc3dda0 | ||
|
4cd76283da | ||
|
cd6fdee99b | ||
|
c831616f3a | ||
|
38c5ba7c79 | ||
|
2696c6472d | ||
|
d590a35ff8 | ||
|
6d2f3d8628 | ||
|
4c85274921 | ||
|
6593096ba0 | ||
|
98b8dc0720 | ||
|
95c0cc2e4b | ||
|
e5fdec970a | ||
|
8746c6e7f2 | ||
|
61b7e66f80 | ||
|
b408f54620 | ||
|
3946cbd4a0 | ||
|
c4b7b79b8b | ||
|
23e76de4f2 | ||
|
15de97e33f | ||
|
78d1ee0222 | ||
|
ac8fb00504 | ||
|
a13cb85f49 | ||
|
a7a906e2cb | ||
|
d705716dc4 | ||
|
76ff702e93 | ||
|
4f81dbe839 | ||
|
796c6bd848 | ||
|
69fe4e1306 | ||
|
2c5155e43d | ||
|
04a86245a1 | ||
|
79491db9f6 | ||
|
5241878e59 | ||
|
30b32af873 | ||
|
da8854cac6 | ||
|
39a035793c | ||
|
0a7bb1822e | ||
|
c4123375e0 | ||
|
52dc66349b | ||
|
9cb21c8557 | ||
|
50db9a29c4 | ||
|
94214ee725 |
39 changed files with 877 additions and 268 deletions
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,6 +1,6 @@
|
||||||
#### 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"):
|
||||||
- [ ] The rule's wiki page does not already cover this (e.g. https://shellcheck.net/wiki/SC2086)
|
- [ ] The rule's wiki page does not already cover this (e.g. https://shellcheck.net/wiki/SC2086)
|
||||||
- [ ] I tried on https://www.shellcheck.net/ and verified that this is still a problem on the latest commit
|
- [ ] I tried on https://www.shellcheck.net/ and verified that this is still a problem on the latest commit
|
||||||
|
|
||||||
|
|
53
.github/workflows/build.yml
vendored
53
.github/workflows/build.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
||||||
sudo apt-get install cabal-install
|
sudo apt-get install cabal-install
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -37,24 +37,47 @@ jobs:
|
||||||
mv dist-newstyle/sdist/*.tar.gz source/source.tar.gz
|
mv dist-newstyle/sdist/*.tar.gz source/source.tar.gz
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: source
|
name: source
|
||||||
path: source/
|
path: source/
|
||||||
|
|
||||||
|
run_tests:
|
||||||
|
name: Run tests
|
||||||
|
needs: package_source
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install ghc cabal-install
|
||||||
|
cabal update
|
||||||
|
|
||||||
|
- name: Unpack source
|
||||||
|
run: |
|
||||||
|
cd source
|
||||||
|
tar xvf source.tar.gz --strip-components=1
|
||||||
|
|
||||||
|
- name: Build and run tests
|
||||||
|
run: |
|
||||||
|
cd source
|
||||||
|
cabal test
|
||||||
|
|
||||||
build_source:
|
build_source:
|
||||||
name: Build Source Code
|
name: Build
|
||||||
needs: package_source
|
needs: package_source
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build: [linux.x86_64, linux.aarch64, linux.armv6hf, darwin.x86_64, darwin.aarch64, windows.x86_64]
|
build: [linux.x86_64, linux.aarch64, linux.armv6hf, linux.riscv64, darwin.x86_64, darwin.aarch64, windows.x86_64]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Build source
|
- name: Build source
|
||||||
run: |
|
run: |
|
||||||
|
@ -63,9 +86,9 @@ jobs:
|
||||||
( cd bin && ../build/run_builder ../source/source.tar.gz ../build/${{matrix.build}} )
|
( cd bin && ../build/run_builder ../source/source.tar.gz ../build/${{matrix.build}} )
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: bin
|
name: ${{matrix.build}}.bin
|
||||||
path: bin/
|
path: bin/
|
||||||
|
|
||||||
package_binary:
|
package_binary:
|
||||||
|
@ -74,25 +97,25 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Work around GitHub permissions bug
|
- name: Work around GitHub permissions bug
|
||||||
run: chmod +x bin/*/shellcheck*
|
run: chmod +x *.bin/*/shellcheck*
|
||||||
|
|
||||||
- name: Package binaries
|
- name: Package binaries
|
||||||
run: |
|
run: |
|
||||||
export TAGS="$(cat source/tags)"
|
export TAGS="$(cat source/tags)"
|
||||||
mkdir -p deploy
|
mkdir -p deploy
|
||||||
cp -r bin/* deploy
|
cp -r *.bin/* deploy
|
||||||
cd deploy
|
cd deploy
|
||||||
../.prepare_deploy
|
../.prepare_deploy
|
||||||
rm -rf */ README* LICENSE*
|
rm -rf */ README* LICENSE*
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: deploy
|
name: deploy
|
||||||
path: deploy/
|
path: deploy/
|
||||||
|
@ -109,10 +132,10 @@ jobs:
|
||||||
sudo apt-get install hub
|
sudo apt-get install hub
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
|
|
||||||
- name: Upload to GitHub
|
- name: Upload to GitHub
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -80,6 +80,7 @@ function multi_arch_docker::main() {
|
||||||
export DOCKER_PLATFORMS='linux/amd64'
|
export DOCKER_PLATFORMS='linux/amd64'
|
||||||
DOCKER_PLATFORMS+=' linux/arm64'
|
DOCKER_PLATFORMS+=' linux/arm64'
|
||||||
DOCKER_PLATFORMS+=' linux/arm/v6'
|
DOCKER_PLATFORMS+=' linux/arm/v6'
|
||||||
|
DOCKER_PLATFORMS+=' linux/riscv64'
|
||||||
|
|
||||||
multi_arch_docker::install_docker_buildx
|
multi_arch_docker::install_docker_buildx
|
||||||
multi_arch_docker::login_to_docker_hub
|
multi_arch_docker::login_to_docker_hub
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -1,3 +1,25 @@
|
||||||
|
## Git
|
||||||
|
### Added
|
||||||
|
- SC2327/SC2328: Warn about capturing the output of redirected commands.
|
||||||
|
- SC2329: Warn when (non-escaping) functions are never invoked.
|
||||||
|
- SC2330: Warn about unsupported glob matches with [[ .. ]] in BusyBox.
|
||||||
|
- SC2331: Suggest using standard -e instead of unary -a in tests.
|
||||||
|
- SC2332: Warn about `[ ! -o opt ]` being unconditionally true in Bash.
|
||||||
|
- SC3062: Warn about bashism `[ -o opt ]`.
|
||||||
|
- Precompiled binaries for Linux riscv64 (linux.riscv64)
|
||||||
|
### Changed
|
||||||
|
- SC2002 about Useless Use Of Cat is now disabled by default. It can be
|
||||||
|
re-enabled with `--enable=useless-use-of-cat` or equivalent directive.
|
||||||
|
- SC2015 about `A && B || C` no longer triggers when B is a test command.
|
||||||
|
- SC3012: Do not warn about `\<` and `\>` in test/[] as specified in POSIX.1-2024
|
||||||
|
### Fixed
|
||||||
|
- SC2218 about function use-before-define is now more accurate.
|
||||||
|
- SC2317 about unreachable commands is now less spammy for nested ones.
|
||||||
|
- SC2292, optional suggestion for [[ ]], now triggers for Busybox.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- SC3013: removed since the operators `-ot/-nt/-ef` are specified in POSIX.1-2024
|
||||||
|
|
||||||
## v0.10.0 - 2024-03-07
|
## v0.10.0 - 2024-03-07
|
||||||
### Added
|
### Added
|
||||||
- Precompiled binaries for macOS ARM64 (darwin.aarch64)
|
- Precompiled binaries for macOS ARM64 (darwin.aarch64)
|
||||||
|
|
|
@ -110,9 +110,11 @@ Services and platforms that have ShellCheck pre-installed and ready to use:
|
||||||
* [Codacy](https://www.codacy.com/)
|
* [Codacy](https://www.codacy.com/)
|
||||||
* [Code Climate](https://codeclimate.com/)
|
* [Code Climate](https://codeclimate.com/)
|
||||||
* [Code Factor](https://www.codefactor.io/)
|
* [Code Factor](https://www.codefactor.io/)
|
||||||
|
* [Codety](https://www.codety.io/) via the [Codety Scanner](https://github.com/codetyio/codety-scanner)
|
||||||
* [CircleCI](https://circleci.com) via the [ShellCheck Orb](https://circleci.com/orbs/registry/orb/circleci/shellcheck)
|
* [CircleCI](https://circleci.com) via the [ShellCheck Orb](https://circleci.com/orbs/registry/orb/circleci/shellcheck)
|
||||||
* [Github](https://github.com/features/actions) (only Linux)
|
* [Github](https://github.com/features/actions) (only Linux)
|
||||||
* [Trunk Check](https://trunk.io/products/check) (universal linter; [allows you to explicitly version your shellcheck install](https://github.com/trunk-io/plugins/blob/bcbb361dcdbe4619af51ea7db474d7fb87540d20/.trunk/trunk.yaml#L32)) via the [shellcheck plugin](https://github.com/trunk-io/plugins/blob/main/linters/shellcheck/plugin.yaml)
|
* [Trunk Check](https://trunk.io/products/check) (universal linter; [allows you to explicitly version your shellcheck install](https://github.com/trunk-io/plugins/blob/bcbb361dcdbe4619af51ea7db474d7fb87540d20/.trunk/trunk.yaml#L32)) via the [shellcheck plugin](https://github.com/trunk-io/plugins/blob/main/linters/shellcheck/plugin.yaml)
|
||||||
|
* [CodeRabbit](https://coderabbit.ai/)
|
||||||
|
|
||||||
Most other services, including [GitLab](https://about.gitlab.com/), let you install
|
Most other services, including [GitLab](https://about.gitlab.com/), let you install
|
||||||
ShellCheck yourself, either through the system's package manager (see [Installing](#installing)),
|
ShellCheck yourself, either through the system's package manager (see [Installing](#installing)),
|
||||||
|
@ -228,11 +230,17 @@ Using the [nix package manager](https://nixos.org/nix):
|
||||||
nix-env -iA nixpkgs.shellcheck
|
nix-env -iA nixpkgs.shellcheck
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Using the [Flox package manager](https://flox.dev/)
|
||||||
|
```sh
|
||||||
|
flox install shellcheck
|
||||||
|
```
|
||||||
|
|
||||||
Alternatively, you can download pre-compiled binaries for the latest release here:
|
Alternatively, you can download pre-compiled binaries for the latest release here:
|
||||||
|
|
||||||
* [Linux, x86_64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.x86_64.tar.xz) (statically linked)
|
* [Linux, x86_64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.x86_64.tar.xz) (statically linked)
|
||||||
* [Linux, armv6hf](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.armv6hf.tar.xz), i.e. Raspberry Pi (statically linked)
|
* [Linux, armv6hf](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.armv6hf.tar.xz), i.e. Raspberry Pi (statically linked)
|
||||||
* [Linux, aarch64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.aarch64.tar.xz) aka ARM64 (statically linked)
|
* [Linux, aarch64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.aarch64.tar.xz) aka ARM64 (statically linked)
|
||||||
|
* [macOS, aarch64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.darwin.aarch64.tar.xz)
|
||||||
* [macOS, x86_64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.darwin.x86_64.tar.xz)
|
* [macOS, x86_64](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.darwin.x86_64.tar.xz)
|
||||||
* [Windows, x86](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.zip)
|
* [Windows, x86](https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.zip)
|
||||||
|
|
||||||
|
|
|
@ -53,12 +53,12 @@ library
|
||||||
bytestring >= 0.10.6 && < 0.13,
|
bytestring >= 0.10.6 && < 0.13,
|
||||||
containers >= 0.5.6 && < 0.8,
|
containers >= 0.5.6 && < 0.8,
|
||||||
deepseq >= 1.4.1 && < 1.6,
|
deepseq >= 1.4.1 && < 1.6,
|
||||||
Diff >= 0.4.0 && < 0.6,
|
Diff >= 0.4.0 && < 1.1,
|
||||||
fgl (>= 5.7.0 && < 5.8.1.0) || (>= 5.8.1.1 && < 5.9),
|
fgl (>= 5.7.0 && < 5.8.1.0) || (>= 5.8.1.1 && < 5.9),
|
||||||
filepath >= 1.4.0 && < 1.5,
|
filepath >= 1.4.0 && < 1.6,
|
||||||
mtl >= 2.2.2 && < 2.4,
|
mtl >= 2.2.2 && < 2.4,
|
||||||
parsec >= 3.1.14 && < 3.2,
|
parsec >= 3.1.14 && < 3.2,
|
||||||
QuickCheck >= 2.14.2 && < 2.15,
|
QuickCheck >= 2.14.2 && < 2.16,
|
||||||
regex-tdfa >= 1.2.0 && < 1.4,
|
regex-tdfa >= 1.2.0 && < 1.4,
|
||||||
transformers >= 0.4.2 && < 0.7,
|
transformers >= 0.4.2 && < 0.7,
|
||||||
|
|
||||||
|
|
|
@ -11,3 +11,7 @@ This makes it simple to build any release without exotic hardware or software.
|
||||||
|
|
||||||
An image can be built and tagged using `build_builder`,
|
An image can be built and tagged using `build_builder`,
|
||||||
and run on a source tarball using `run_builder`.
|
and run on a source tarball using `run_builder`.
|
||||||
|
|
||||||
|
Tip: Are you developing an image that relies on QEmu usermode emulation?
|
||||||
|
It's easy to accidentally depend on binfmt\_misc on the host OS.
|
||||||
|
Do a `echo 0 | sudo tee /proc/sys/fs/binfmt_misc/status` before testing.
|
||||||
|
|
|
@ -4,7 +4,6 @@ set -xe
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
chmod +x striptests && ./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
|
||||||
( IFS=';'; cabal build $CABALOPTS )
|
( IFS=';'; cabal build $CABALOPTS )
|
||||||
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
||||||
ls -l "$TARGETNAME"
|
ls -l "$TARGETNAME"
|
||||||
|
|
|
@ -4,7 +4,6 @@ set -xe
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
chmod +x striptests && ./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
|
||||||
( IFS=';'; cabal build $CABALOPTS )
|
( IFS=';'; cabal build $CABALOPTS )
|
||||||
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
||||||
ls -l "$TARGETNAME"
|
ls -l "$TARGETNAME"
|
||||||
|
|
|
@ -12,11 +12,15 @@ RUN apt-get update && apt-get install -y llvm gcc-$TARGET
|
||||||
|
|
||||||
# The rest are from 22.10
|
# The rest are from 22.10
|
||||||
RUN sed -e 's/focal/kinetic/g' -i /etc/apt/sources.list
|
RUN sed -e 's/focal/kinetic/g' -i /etc/apt/sources.list
|
||||||
|
# Kinetic does not receive updates anymore, switch to last available
|
||||||
|
RUN sed -e 's/archive.ubuntu.com/old-releases.ubuntu.com/g' -i /etc/apt/sources.list
|
||||||
|
RUN sed -e 's/security.ubuntu.com/old-releases.ubuntu.com/g' -i /etc/apt/sources.list
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y ghc alex happy automake autoconf build-essential curl qemu-user-static
|
RUN apt-get update && apt-get install -y ghc alex happy automake autoconf build-essential curl qemu-user-static
|
||||||
|
|
||||||
# Build GHC
|
# Build GHC
|
||||||
WORKDIR /ghc
|
WORKDIR /ghc
|
||||||
RUN curl -L "https://downloads.haskell.org/~ghc/9.2.5/ghc-9.2.5-src.tar.xz" | tar xJ --strip-components=1
|
RUN curl -L "https://downloads.haskell.org/~ghc/9.2.8/ghc-9.2.8-src.tar.xz" | tar xJ --strip-components=1
|
||||||
RUN ./boot && ./configure --host x86_64-linux-gnu --build x86_64-linux-gnu --target "$TARGET"
|
RUN ./boot && ./configure --host x86_64-linux-gnu --build x86_64-linux-gnu --target "$TARGET"
|
||||||
RUN cp mk/flavours/quick-cross.mk mk/build.mk && make -j "$(nproc)"
|
RUN cp mk/flavours/quick-cross.mk mk/build.mk && make -j "$(nproc)"
|
||||||
RUN make install
|
RUN make install
|
||||||
|
@ -24,7 +28,7 @@ RUN curl -L "https://downloads.haskell.org/~cabal/cabal-install-3.9.0.0/cabal-in
|
||||||
|
|
||||||
# Due to an apparent cabal bug, we specify our options directly to cabal
|
# 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
|
# It won't reuse caches if ghc-options are specified in ~/.cabal/config
|
||||||
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections -optc-fPIC;--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg"
|
ENV CABALOPTS "--ghc-options;-split-sections -optc-Os -optc-Wl,--gc-sections -optc-fPIC;--with-ghc=$TARGET-ghc;--with-hc-pkg=$TARGET-ghc-pkg;-c;hashable -arch-native"
|
||||||
|
|
||||||
# Prebuild the dependencies
|
# Prebuild the dependencies
|
||||||
RUN cabal update && IFS=';' && cabal install --dependencies-only $CABALOPTS ShellCheck
|
RUN cabal update && IFS=';' && cabal install --dependencies-only $CABALOPTS ShellCheck
|
||||||
|
|
|
@ -4,7 +4,6 @@ set -xe
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
chmod +x striptests && ./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
|
||||||
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
( IFS=';'; cabal build $CABALOPTS --enable-executable-static )
|
||||||
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
||||||
ls -l "$TARGETNAME"
|
ls -l "$TARGETNAME"
|
||||||
|
|
|
@ -1,25 +1,7 @@
|
||||||
# I've again spent days trying to get a working armv6hf compiler going.
|
# This Docker file uses a custom QEmu fork with patches to follow execve
|
||||||
# God only knows how many recompilations of GCC, GHC, libraries, and
|
# to build all of ShellCheck emulated.
|
||||||
# ShellCheck itself, has gone into it.
|
|
||||||
#
|
|
||||||
# I tried Debian's toolchain. I tried my custom one built according to
|
|
||||||
# RPi `gcc -v`. I tried GHC9, glibc, musl, registerised vs not, but
|
|
||||||
# nothing has yielded an armv6hf binary that does not immediately
|
|
||||||
# segfault on qemu-arm-static or the RPi itself.
|
|
||||||
#
|
|
||||||
# I then tried the same but with armv7hf. Same story.
|
|
||||||
#
|
|
||||||
# Emulating the entire userspace with balenalib again? Very strange build
|
|
||||||
# failures where programs would fail to execute with > ~100 arguments.
|
|
||||||
#
|
|
||||||
# Finally, creating our own appears to work when using a custom QEmu
|
|
||||||
# patched to follow execve calls.
|
|
||||||
#
|
|
||||||
# PS: $100 bounty for getting a RPi1 compatible static build going
|
|
||||||
# with cross-compilation, similar to what the aarch64 build does.
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
ENV TARGETNAME linux.armv6hf
|
ENV TARGETNAME linux.armv6hf
|
||||||
|
|
||||||
|
@ -27,34 +9,34 @@ ENV TARGETNAME linux.armv6hf
|
||||||
USER root
|
USER root
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y build-essential git ninja-build python3 pkg-config libglib2.0-dev libpixman-1-dev
|
RUN apt-get install -y --no-install-recommends build-essential git ninja-build python3 pkg-config libglib2.0-dev libpixman-1-dev python3-setuptools ca-certificates debootstrap
|
||||||
WORKDIR /build
|
WORKDIR /qemu
|
||||||
RUN git clone --depth 1 https://github.com/koalaman/qemu
|
RUN git clone --depth 1 https://github.com/koalaman/qemu .
|
||||||
RUN cd qemu && ./configure --static && cd build && ninja qemu-arm
|
RUN ./configure --static --disable-werror && cd build && ninja qemu-arm
|
||||||
RUN cp qemu/build/qemu-arm /build/qemu-arm-static
|
|
||||||
ENV QEMU_EXECVE 1
|
ENV QEMU_EXECVE 1
|
||||||
|
|
||||||
|
# Convenience utility
|
||||||
|
COPY scutil /bin/scutil
|
||||||
|
COPY scutil /chroot/bin/scutil
|
||||||
|
RUN chmod +x /bin/scutil /chroot/bin/scutil
|
||||||
|
|
||||||
# Set up an armv6 userspace
|
# Set up an armv6 userspace
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN apt-get install -y debootstrap qemu-user-static
|
RUN debootstrap --arch armhf --variant=minbase --foreign bookworm /chroot http://mirrordirector.raspbian.org/raspbian
|
||||||
# We expect this to fail if the host doesn't have binfmt qemu support
|
RUN cp /qemu/build/qemu-arm /chroot/bin/qemu
|
||||||
RUN qemu-debootstrap --arch armhf bullseye pi http://mirrordirector.raspbian.org/raspbian || [ -e /pi/etc/issue ]
|
RUN scutil emu /debootstrap/debootstrap --second-stage
|
||||||
RUN cp /build/qemu-arm-static /pi/usr/bin/qemu-arm-static
|
|
||||||
RUN printf > /bin/pirun '%s\n' '#!/bin/sh' 'chroot /pi /usr/bin/qemu-arm-static /usr/bin/env "$@"' && chmod +x /bin/pirun
|
|
||||||
# If the debootstrap process didn't finish, continue it
|
|
||||||
RUN [ ! -e /pi/debootstrap ] || pirun '/debootstrap/debootstrap' --second-stage
|
|
||||||
|
|
||||||
# Install deps in the chroot
|
# Install deps in the chroot
|
||||||
RUN pirun apt-get update
|
RUN scutil emu apt-get update
|
||||||
RUN pirun apt-get install -y ghc cabal-install
|
RUN scutil emu apt-get install -y --no-install-recommends ghc cabal-install
|
||||||
|
RUN scutil emu cabal update
|
||||||
|
|
||||||
# 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
|
# Generated with `cabal freeze --constraint 'hashable -arch-native'`
|
||||||
RUN IFS=";" && pirun cabal install --dependencies-only $CABALOPTS ShellCheck
|
COPY cabal.project.freeze /chroot/etc
|
||||||
RUN IFS=';' && pirun cabal install $CABALOPTS --lib fgl
|
RUN IFS=";" && scutil install_from_freeze /chroot/etc/cabal.project.freeze emu cabal install $CABALOPTS
|
||||||
|
|
||||||
# Copy the build script
|
# Copy the build script
|
||||||
WORKDIR /pi/scratch
|
COPY build /chroot/bin
|
||||||
COPY build /pi/usr/bin
|
ENTRYPOINT ["/bin/scutil", "emu", "/bin/build"]
|
||||||
ENTRYPOINT ["/bin/pirun", "/usr/bin/build"]
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -xe
|
set -xe
|
||||||
cd /scratch
|
mkdir /scratch && cd /scratch
|
||||||
{
|
{
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
|
cp /etc/cabal.project.freeze .
|
||||||
chmod +x striptests && ./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
|
||||||
|
|
93
build/linux.armv6hf/cabal.project.freeze
Normal file
93
build/linux.armv6hf/cabal.project.freeze
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
active-repositories: hackage.haskell.org:merge
|
||||||
|
constraints: any.Diff ==0.5,
|
||||||
|
any.OneTuple ==0.4.2,
|
||||||
|
any.QuickCheck ==2.14.3,
|
||||||
|
QuickCheck -old-random +templatehaskell,
|
||||||
|
any.StateVar ==1.2.2,
|
||||||
|
any.aeson ==2.2.3.0,
|
||||||
|
aeson +ordered-keymap,
|
||||||
|
any.array ==0.5.4.0,
|
||||||
|
any.assoc ==1.1.1,
|
||||||
|
assoc -tagged,
|
||||||
|
any.base ==4.15.1.0,
|
||||||
|
any.base-orphans ==0.9.2,
|
||||||
|
any.bifunctors ==5.6.2,
|
||||||
|
bifunctors +tagged,
|
||||||
|
any.binary ==0.8.8.0,
|
||||||
|
any.bytestring ==0.10.12.1,
|
||||||
|
any.character-ps ==0.1,
|
||||||
|
any.comonad ==5.0.8,
|
||||||
|
comonad +containers +distributive +indexed-traversable,
|
||||||
|
any.containers ==0.6.4.1,
|
||||||
|
any.contravariant ==1.5.5,
|
||||||
|
contravariant +semigroups +statevar +tagged,
|
||||||
|
any.data-array-byte ==0.1.0.1,
|
||||||
|
any.data-fix ==0.3.3,
|
||||||
|
any.deepseq ==1.4.5.0,
|
||||||
|
any.directory ==1.3.6.2,
|
||||||
|
any.distributive ==0.6.2.1,
|
||||||
|
distributive +semigroups +tagged,
|
||||||
|
any.dlist ==1.0,
|
||||||
|
dlist -werror,
|
||||||
|
any.exceptions ==0.10.4,
|
||||||
|
any.fgl ==5.8.2.0,
|
||||||
|
fgl +containers042,
|
||||||
|
any.filepath ==1.4.2.1,
|
||||||
|
any.foldable1-classes-compat ==0.1,
|
||||||
|
foldable1-classes-compat +tagged,
|
||||||
|
any.generically ==0.1.1,
|
||||||
|
any.ghc-bignum ==1.1,
|
||||||
|
any.ghc-boot-th ==9.0.2,
|
||||||
|
any.ghc-prim ==0.7.0,
|
||||||
|
any.hashable ==1.4.6.0,
|
||||||
|
hashable -arch-native +integer-gmp -random-initial-seed,
|
||||||
|
any.indexed-traversable ==0.1.4,
|
||||||
|
any.indexed-traversable-instances ==0.1.2,
|
||||||
|
any.integer-conversion ==0.1.1,
|
||||||
|
any.integer-logarithms ==1.0.3.1,
|
||||||
|
integer-logarithms -check-bounds +integer-gmp,
|
||||||
|
any.mtl ==2.2.2,
|
||||||
|
any.network-uri ==2.6.4.2,
|
||||||
|
any.parsec ==3.1.14.0,
|
||||||
|
any.pretty ==1.1.3.6,
|
||||||
|
any.primitive ==0.9.0.0,
|
||||||
|
any.process ==1.6.13.2,
|
||||||
|
any.random ==1.2.1.2,
|
||||||
|
any.regex-base ==0.94.0.2,
|
||||||
|
any.regex-tdfa ==1.3.2.2,
|
||||||
|
regex-tdfa +doctest -force-o2,
|
||||||
|
any.rts ==1.0.2,
|
||||||
|
any.scientific ==0.3.8.0,
|
||||||
|
scientific -integer-simple,
|
||||||
|
any.semialign ==1.3.1,
|
||||||
|
semialign +semigroupoids,
|
||||||
|
any.semigroupoids ==6.0.1,
|
||||||
|
semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers,
|
||||||
|
any.splitmix ==0.1.0.5,
|
||||||
|
splitmix -optimised-mixer,
|
||||||
|
any.stm ==2.5.0.0,
|
||||||
|
any.strict ==0.5,
|
||||||
|
any.tagged ==0.8.8,
|
||||||
|
tagged +deepseq +transformers,
|
||||||
|
any.template-haskell ==2.17.0.0,
|
||||||
|
any.text ==1.2.5.0,
|
||||||
|
any.text-iso8601 ==0.1.1,
|
||||||
|
any.text-short ==0.1.6,
|
||||||
|
text-short -asserts,
|
||||||
|
any.th-abstraction ==0.7.0.0,
|
||||||
|
any.th-compat ==0.1.5,
|
||||||
|
any.these ==1.2.1,
|
||||||
|
any.time ==1.9.3,
|
||||||
|
any.time-compat ==1.9.7,
|
||||||
|
any.transformers ==0.5.6.2,
|
||||||
|
any.transformers-compat ==0.7.2,
|
||||||
|
transformers-compat -five +five-three -four +generic-deriving +mtl -three -two,
|
||||||
|
any.unix ==2.7.2.2,
|
||||||
|
any.unordered-containers ==0.2.20,
|
||||||
|
unordered-containers -debug,
|
||||||
|
any.uuid-types ==1.0.6,
|
||||||
|
any.vector ==0.13.1.0,
|
||||||
|
vector +boundschecks -internalchecks -unsafechecks -wall,
|
||||||
|
any.vector-stream ==0.1.0.1,
|
||||||
|
any.witherable ==0.5
|
||||||
|
index-state: hackage.haskell.org 2024-06-18T02:21:19Z
|
48
build/linux.armv6hf/scutil
Normal file
48
build/linux.armv6hf/scutil
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#!/bin/dash
|
||||||
|
# Various ShellCheck build utility functions
|
||||||
|
|
||||||
|
# Generally set a ulimit to avoid QEmu using too much memory
|
||||||
|
ulimit -v "$((10*1024*1024))"
|
||||||
|
# If we happen to invoke or run under QEmu, make sure to follow execve.
|
||||||
|
# This requires a patched QEmu.
|
||||||
|
export QEMU_EXECVE=1
|
||||||
|
|
||||||
|
# Retry a command until it succeeds
|
||||||
|
# Usage: scutil retry 3 mycmd
|
||||||
|
retry() {
|
||||||
|
n="$1"
|
||||||
|
ret=1
|
||||||
|
shift
|
||||||
|
while [ "$n" -gt 0 ]
|
||||||
|
do
|
||||||
|
"$@"
|
||||||
|
ret=$?
|
||||||
|
[ "$ret" = 0 ] && break
|
||||||
|
n=$((n-1))
|
||||||
|
done
|
||||||
|
return "$ret"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install all dependencies from a freeze file
|
||||||
|
# Usage: scutil install_from_freeze /path/cabal.project.freeze cabal install
|
||||||
|
install_from_freeze() {
|
||||||
|
linefeed=$(printf '\nx')
|
||||||
|
linefeed=${linefeed%x}
|
||||||
|
flags=$(
|
||||||
|
sed 's/constraints:/&\n /' "$1" |
|
||||||
|
grep -vw -e rts -e base |
|
||||||
|
sed -n -e 's/^ *\([^,]*\).*/\1/p' |
|
||||||
|
sed -e 's/any\.\([^ ]*\) ==\(.*\)/\1-\2/; te; s/.*/--constraint\n&/; :e')
|
||||||
|
shift
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
( IFS=$linefeed; set -x; "$@" $flags )
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run a command under emulation.
|
||||||
|
# This assumes the correct emulator is named 'qemu' and the chroot is /chroot
|
||||||
|
# Usage: scutil emu echo "Hello World"
|
||||||
|
emu() {
|
||||||
|
chroot /chroot /bin/qemu /usr/bin/env "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
"$@"
|
46
build/linux.riscv64/Dockerfile
Normal file
46
build/linux.riscv64/Dockerfile
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
ENV TARGETNAME linux.riscv64
|
||||||
|
ENV TARGET riscv64-linux-gnu
|
||||||
|
|
||||||
|
USER root
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Init base
|
||||||
|
RUN apt-get update -y
|
||||||
|
|
||||||
|
# Install qemu
|
||||||
|
RUN apt-get install -y --no-install-recommends build-essential ninja-build python3 pkg-config libglib2.0-dev libpixman-1-dev curl ca-certificates python3-virtualenv git python3-setuptools debootstrap
|
||||||
|
WORKDIR /qemu
|
||||||
|
RUN git clone --depth 1 https://github.com/koalaman/qemu .
|
||||||
|
RUN ./configure --target-list=riscv64-linux-user --static --disable-system --disable-pie --disable-werror
|
||||||
|
RUN cd build && ninja qemu-riscv64
|
||||||
|
ENV QEMU_EXECVE 1
|
||||||
|
|
||||||
|
# Convenience utility
|
||||||
|
COPY scutil /bin/scutil
|
||||||
|
# We have to copy to /usr/bin because debootstrap will try to symlink /bin and fail if it exists
|
||||||
|
COPY scutil /chroot/usr/bin/scutil
|
||||||
|
RUN chmod +x /bin/scutil /chroot/usr/bin/scutil
|
||||||
|
|
||||||
|
# Set up a riscv64 userspace
|
||||||
|
WORKDIR /
|
||||||
|
RUN debootstrap --arch=riscv64 --variant=minbase --components=main,universe --foreign noble /chroot http://ports.ubuntu.com/ubuntu-ports
|
||||||
|
RUN cp /qemu/build/qemu-riscv64 /chroot/bin/qemu
|
||||||
|
RUN scutil emu /debootstrap/debootstrap --second-stage
|
||||||
|
|
||||||
|
# Install deps in the chroot
|
||||||
|
RUN scutil emu apt-get update
|
||||||
|
RUN scutil emu apt-get install -y --no-install-recommends ghc cabal-install
|
||||||
|
RUN scutil emu cabal update
|
||||||
|
|
||||||
|
# Generated with: cabal freeze -c 'hashable -arch-native'. We put it in /etc so cabal won't find it.
|
||||||
|
COPY cabal.project.freeze /chroot/etc
|
||||||
|
|
||||||
|
# Build all dependencies from the freeze file. The emulator segfaults at random,
|
||||||
|
# so retry a few times.
|
||||||
|
RUN scutil install_from_freeze /chroot/etc/cabal.project.freeze retry 5 emu cabal install --keep-going
|
||||||
|
|
||||||
|
# Copy the build script
|
||||||
|
COPY build /chroot/bin/build
|
||||||
|
ENTRYPOINT ["/bin/scutil", "emu", "/bin/build"]
|
21
build/linux.riscv64/build
Executable file
21
build/linux.riscv64/build
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -xe
|
||||||
|
IFS=';'
|
||||||
|
{
|
||||||
|
mkdir -p /tmp/scratch
|
||||||
|
cd /tmp/scratch
|
||||||
|
tar xzv --strip-components=1
|
||||||
|
chmod +x striptests && ./striptests
|
||||||
|
# Use a freeze file to ensure we use the same dependencies we cached during
|
||||||
|
# the docker image build. We don't want to spend time compiling anything new.
|
||||||
|
cp /etc/cabal.project.freeze .
|
||||||
|
mkdir "$TARGETNAME"
|
||||||
|
# Retry in case of random segfault
|
||||||
|
scutil retry 3 cabal build --enable-executable-static
|
||||||
|
find . -name shellcheck -type f -exec mv {} "$TARGETNAME/" \;
|
||||||
|
ls -l "$TARGETNAME"
|
||||||
|
"$TARGET-strip" -s "$TARGETNAME/shellcheck"
|
||||||
|
ls -l "$TARGETNAME"
|
||||||
|
"$TARGETNAME/shellcheck" --version
|
||||||
|
} >&2
|
||||||
|
tar czv "$TARGETNAME"
|
93
build/linux.riscv64/cabal.project.freeze
Normal file
93
build/linux.riscv64/cabal.project.freeze
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
active-repositories: hackage.haskell.org:merge
|
||||||
|
constraints: any.Diff ==0.5,
|
||||||
|
any.OneTuple ==0.4.2,
|
||||||
|
any.QuickCheck ==2.14.3,
|
||||||
|
QuickCheck -old-random +templatehaskell,
|
||||||
|
any.StateVar ==1.2.2,
|
||||||
|
any.aeson ==2.2.3.0,
|
||||||
|
aeson +ordered-keymap,
|
||||||
|
any.array ==0.5.4.0,
|
||||||
|
any.assoc ==1.1.1,
|
||||||
|
assoc -tagged,
|
||||||
|
any.base ==4.17.2.0,
|
||||||
|
any.base-orphans ==0.9.2,
|
||||||
|
any.bifunctors ==5.6.2,
|
||||||
|
bifunctors +tagged,
|
||||||
|
any.binary ==0.8.9.1,
|
||||||
|
any.bytestring ==0.11.5.2,
|
||||||
|
any.character-ps ==0.1,
|
||||||
|
any.comonad ==5.0.8,
|
||||||
|
comonad +containers +distributive +indexed-traversable,
|
||||||
|
any.containers ==0.6.7,
|
||||||
|
any.contravariant ==1.5.5,
|
||||||
|
contravariant +semigroups +statevar +tagged,
|
||||||
|
any.data-fix ==0.3.3,
|
||||||
|
any.deepseq ==1.4.8.0,
|
||||||
|
any.directory ==1.3.7.1,
|
||||||
|
any.distributive ==0.6.2.1,
|
||||||
|
distributive +semigroups +tagged,
|
||||||
|
any.dlist ==1.0,
|
||||||
|
dlist -werror,
|
||||||
|
any.exceptions ==0.10.5,
|
||||||
|
any.fgl ==5.8.2.0,
|
||||||
|
fgl +containers042,
|
||||||
|
any.filepath ==1.4.2.2,
|
||||||
|
any.foldable1-classes-compat ==0.1,
|
||||||
|
foldable1-classes-compat +tagged,
|
||||||
|
any.generically ==0.1.1,
|
||||||
|
any.ghc-bignum ==1.3,
|
||||||
|
any.ghc-boot-th ==9.4.7,
|
||||||
|
any.ghc-prim ==0.9.1,
|
||||||
|
any.hashable ==1.4.6.0,
|
||||||
|
hashable -arch-native +integer-gmp -random-initial-seed,
|
||||||
|
any.indexed-traversable ==0.1.4,
|
||||||
|
any.indexed-traversable-instances ==0.1.2,
|
||||||
|
any.integer-conversion ==0.1.1,
|
||||||
|
any.integer-logarithms ==1.0.3.1,
|
||||||
|
integer-logarithms -check-bounds +integer-gmp,
|
||||||
|
any.mtl ==2.2.2,
|
||||||
|
any.network-uri ==2.6.4.2,
|
||||||
|
any.os-string ==2.0.3,
|
||||||
|
any.parsec ==3.1.16.1,
|
||||||
|
any.pretty ==1.1.3.6,
|
||||||
|
any.primitive ==0.9.0.0,
|
||||||
|
any.process ==1.6.17.0,
|
||||||
|
any.random ==1.2.1.2,
|
||||||
|
any.regex-base ==0.94.0.2,
|
||||||
|
any.regex-tdfa ==1.3.2.2,
|
||||||
|
regex-tdfa +doctest -force-o2,
|
||||||
|
any.rts ==1.0.2,
|
||||||
|
any.scientific ==0.3.8.0,
|
||||||
|
scientific -integer-simple,
|
||||||
|
any.semialign ==1.3.1,
|
||||||
|
semialign +semigroupoids,
|
||||||
|
any.semigroupoids ==6.0.1,
|
||||||
|
semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers,
|
||||||
|
any.splitmix ==0.1.0.5,
|
||||||
|
splitmix -optimised-mixer,
|
||||||
|
any.stm ==2.5.1.0,
|
||||||
|
any.strict ==0.5,
|
||||||
|
any.tagged ==0.8.8,
|
||||||
|
tagged +deepseq +transformers,
|
||||||
|
any.template-haskell ==2.19.0.0,
|
||||||
|
any.text ==2.0.2,
|
||||||
|
any.text-iso8601 ==0.1.1,
|
||||||
|
any.text-short ==0.1.6,
|
||||||
|
text-short -asserts,
|
||||||
|
any.th-abstraction ==0.7.0.0,
|
||||||
|
any.th-compat ==0.1.5,
|
||||||
|
any.these ==1.2.1,
|
||||||
|
any.time ==1.12.2,
|
||||||
|
any.time-compat ==1.9.7,
|
||||||
|
any.transformers ==0.5.6.2,
|
||||||
|
any.transformers-compat ==0.7.2,
|
||||||
|
transformers-compat -five +five-three -four +generic-deriving +mtl -three -two,
|
||||||
|
any.unix ==2.7.3,
|
||||||
|
any.unordered-containers ==0.2.20,
|
||||||
|
unordered-containers -debug,
|
||||||
|
any.uuid-types ==1.0.6,
|
||||||
|
any.vector ==0.13.1.0,
|
||||||
|
vector +boundschecks -internalchecks -unsafechecks -wall,
|
||||||
|
any.vector-stream ==0.1.0.1,
|
||||||
|
any.witherable ==0.5
|
||||||
|
index-state: hackage.haskell.org 2024-06-17T00:48:51Z
|
1
build/linux.riscv64/tag
Normal file
1
build/linux.riscv64/tag
Normal file
|
@ -0,0 +1 @@
|
||||||
|
koalaman/scbuilder-linux-riscv64
|
|
@ -1,4 +1,8 @@
|
||||||
FROM alpine:latest
|
FROM alpine:3.16
|
||||||
|
# alpine:3.16 (GHC 9.0.1): 5.8 megabytes
|
||||||
|
# alpine:3.17 (GHC 9.0.2): 15.0 megabytes
|
||||||
|
# alpine:3.18 (GHC 9.4.4): 29.0 megabytes
|
||||||
|
# alpine:3.19 (GHC 9.4.7): 29.0 megabytes
|
||||||
|
|
||||||
ENV TARGETNAME linux.x86_64
|
ENV TARGETNAME linux.x86_64
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ WORKDIR /haskell
|
||||||
RUN curl -L "https://downloads.haskell.org/~ghc/8.10.4/ghc-8.10.4-x86_64-unknown-mingw32.tar.xz" | tar xJ --strip-components=1
|
RUN curl -L "https://downloads.haskell.org/~ghc/8.10.4/ghc-8.10.4-x86_64-unknown-mingw32.tar.xz" | tar xJ --strip-components=1
|
||||||
WORKDIR /haskell/bin
|
WORKDIR /haskell/bin
|
||||||
RUN curl -L "https://downloads.haskell.org/~cabal/cabal-install-3.2.0.0/cabal-install-3.2.0.0-x86_64-unknown-mingw32.zip" | busybox unzip -
|
RUN curl -L "https://downloads.haskell.org/~cabal/cabal-install-3.2.0.0/cabal-install-3.2.0.0-x86_64-unknown-mingw32.zip" | busybox unzip -
|
||||||
RUN curl -L "https://curl.se/windows/dl-7.84.0/curl-7.84.0-win64-mingw.zip" | busybox unzip - && mv curl-7.84.0-win64-mingw/bin/* .
|
RUN curl -L "https://curl.se/windows/dl-8.7.1_7/curl-8.7.1_7-win64-mingw.zip" | busybox unzip - && mv curl-*-win64-mingw/bin/* .
|
||||||
ENV WINEPATH /haskell/bin
|
ENV WINEPATH /haskell/bin
|
||||||
|
|
||||||
# It's unknown whether Cabal on Windows suffers from the same issue
|
# It's unknown whether Cabal on Windows suffers from the same issue
|
||||||
|
|
|
@ -8,7 +8,6 @@ set -xe
|
||||||
tar xzv --strip-components=1
|
tar xzv --strip-components=1
|
||||||
chmod +x striptests && ./striptests
|
chmod +x striptests && ./striptests
|
||||||
mkdir "$TARGETNAME"
|
mkdir "$TARGETNAME"
|
||||||
cabal update
|
|
||||||
( IFS=';'; cabal build $CABALOPTS )
|
( IFS=';'; cabal build $CABALOPTS )
|
||||||
find dist*/ -name shellcheck.exe -type f -ls -exec mv {} "$TARGETNAME/" \;
|
find dist*/ -name shellcheck.exe -type f -ls -exec mv {} "$TARGETNAME/" \;
|
||||||
ls -l "$TARGETNAME"
|
ls -l "$TARGETNAME"
|
||||||
|
|
|
@ -78,7 +78,7 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts.
|
||||||
|
|
||||||
: Don't try to look for .shellcheckrc configuration files.
|
: Don't try to look for .shellcheckrc configuration files.
|
||||||
|
|
||||||
--rcfile\ RCFILE
|
**--rcfile** *RCFILE*
|
||||||
|
|
||||||
: Prefer the specified configuration file over searching for one
|
: Prefer the specified configuration file over searching for one
|
||||||
in the default locations.
|
in the default locations.
|
||||||
|
@ -317,7 +317,7 @@ Here is an example `.shellcheckrc`:
|
||||||
disable=SC2236
|
disable=SC2236
|
||||||
|
|
||||||
If no `.shellcheckrc` is found in any of the parent directories, ShellCheck
|
If no `.shellcheckrc` is found in any of the parent directories, ShellCheck
|
||||||
will look in `~/.shellcheckrc` followed by the XDG config directory
|
will look in `~/.shellcheckrc` followed by the `$XDG_CONFIG_HOME`
|
||||||
(usually `~/.config/shellcheckrc`) on Unix, or `%APPDATA%/shellcheckrc` on
|
(usually `~/.config/shellcheckrc`) on Unix, or `%APPDATA%/shellcheckrc` on
|
||||||
Windows. Only the first file found will be used.
|
Windows. Only the first file found will be used.
|
||||||
|
|
||||||
|
@ -403,4 +403,4 @@ see https://gnu.org/licenses/gpl.html
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
|
|
||||||
sh(1) bash(1)
|
sh(1) bash(1) dash(1) ksh(1)
|
||||||
|
|
|
@ -138,7 +138,7 @@ data InnerToken t =
|
||||||
| Inner_T_WhileExpression [t] [t]
|
| Inner_T_WhileExpression [t] [t]
|
||||||
| Inner_T_Annotation [Annotation] t
|
| Inner_T_Annotation [Annotation] t
|
||||||
| Inner_T_Pipe String
|
| Inner_T_Pipe String
|
||||||
| Inner_T_CoProc (Maybe String) t
|
| Inner_T_CoProc (Maybe Token) t
|
||||||
| Inner_T_CoProcBody t
|
| Inner_T_CoProcBody t
|
||||||
| Inner_T_Include t
|
| Inner_T_Include t
|
||||||
| Inner_T_SourceCommand t t
|
| Inner_T_SourceCommand t t
|
||||||
|
@ -206,7 +206,7 @@ pattern T_Annotation id anns t = OuterToken id (Inner_T_Annotation anns t)
|
||||||
pattern T_Arithmetic id c = OuterToken id (Inner_T_Arithmetic c)
|
pattern T_Arithmetic id c = OuterToken id (Inner_T_Arithmetic c)
|
||||||
pattern T_Array id t = OuterToken id (Inner_T_Array t)
|
pattern T_Array id t = OuterToken id (Inner_T_Array t)
|
||||||
pattern TA_Sequence id l = OuterToken id (Inner_TA_Sequence l)
|
pattern TA_Sequence id l = OuterToken id (Inner_TA_Sequence l)
|
||||||
pattern TA_Parentesis id t = OuterToken id (Inner_TA_Parenthesis t)
|
pattern TA_Parenthesis id t = OuterToken id (Inner_TA_Parenthesis t)
|
||||||
pattern T_Assignment id mode var indices value = OuterToken id (Inner_T_Assignment mode var indices value)
|
pattern T_Assignment id mode var indices value = OuterToken id (Inner_T_Assignment mode var indices value)
|
||||||
pattern TA_Trinary id t1 t2 t3 = OuterToken id (Inner_TA_Trinary t1 t2 t3)
|
pattern TA_Trinary id t1 t2 t3 = OuterToken id (Inner_TA_Trinary t1 t2 t3)
|
||||||
pattern TA_Unary id op t1 = OuterToken id (Inner_TA_Unary op t1)
|
pattern TA_Unary id op t1 = OuterToken id (Inner_TA_Unary op t1)
|
||||||
|
@ -259,7 +259,7 @@ pattern T_Subshell id l = OuterToken id (Inner_T_Subshell l)
|
||||||
pattern T_UntilExpression id c l = OuterToken id (Inner_T_UntilExpression c l)
|
pattern T_UntilExpression id c l = OuterToken id (Inner_T_UntilExpression c l)
|
||||||
pattern T_WhileExpression id c l = OuterToken id (Inner_T_WhileExpression c l)
|
pattern T_WhileExpression id c l = OuterToken id (Inner_T_WhileExpression c l)
|
||||||
|
|
||||||
{-# COMPLETE T_AND_IF, T_Bang, T_Case, TC_Empty, T_CLOBBER, T_DGREAT, T_DLESS, T_DLESSDASH, T_Do, T_DollarSingleQuoted, T_Done, T_DSEMI, T_Elif, T_Else, T_EOF, T_Esac, T_Fi, T_For, T_Glob, T_GREATAND, T_Greater, T_If, T_In, T_Lbrace, T_Less, T_LESSAND, T_LESSGREAT, T_Literal, T_Lparen, T_NEWLINE, T_OR_IF, T_ParamSubSpecialChar, T_Pipe, T_Rbrace, T_Rparen, T_Select, T_Semi, T_SingleQuoted, T_Then, T_UnparsedIndex, T_Until, T_While, TA_Assignment, TA_Binary, TA_Expansion, T_AndIf, T_Annotation, T_Arithmetic, T_Array, TA_Sequence, TA_Parentesis, T_Assignment, TA_Trinary, TA_Unary, TA_Variable, T_Backgrounded, T_Backticked, T_Banged, T_BatsTest, T_BraceExpansion, T_BraceGroup, TC_And, T_CaseExpression, TC_Binary, TC_Group, TC_Nullary, T_Condition, T_CoProcBody, T_CoProc, TC_Or, TC_Unary, T_DollarArithmetic, T_DollarBraceCommandExpansion, T_DollarBraced, T_DollarBracket, T_DollarDoubleQuoted, T_DollarExpansion, T_DoubleQuoted, T_Extglob, T_FdRedirect, T_ForArithmetic, T_ForIn, T_Function, T_HereDoc, T_HereString, T_IfExpression, T_Include, T_IndexedElement, T_IoDuplicate, T_IoFile, T_NormalWord, T_OrIf, T_Pipeline, T_ProcSub, T_Redirecting, T_Script, T_SelectIn, T_SimpleCommand, T_SourceCommand, T_Subshell, T_UntilExpression, T_WhileExpression #-}
|
{-# COMPLETE T_AND_IF, T_Bang, T_Case, TC_Empty, T_CLOBBER, T_DGREAT, T_DLESS, T_DLESSDASH, T_Do, T_DollarSingleQuoted, T_Done, T_DSEMI, T_Elif, T_Else, T_EOF, T_Esac, T_Fi, T_For, T_Glob, T_GREATAND, T_Greater, T_If, T_In, T_Lbrace, T_Less, T_LESSAND, T_LESSGREAT, T_Literal, T_Lparen, T_NEWLINE, T_OR_IF, T_ParamSubSpecialChar, T_Pipe, T_Rbrace, T_Rparen, T_Select, T_Semi, T_SingleQuoted, T_Then, T_UnparsedIndex, T_Until, T_While, TA_Assignment, TA_Binary, TA_Expansion, T_AndIf, T_Annotation, T_Arithmetic, T_Array, TA_Sequence, TA_Parenthesis, T_Assignment, TA_Trinary, TA_Unary, TA_Variable, T_Backgrounded, T_Backticked, T_Banged, T_BatsTest, T_BraceExpansion, T_BraceGroup, TC_And, T_CaseExpression, TC_Binary, TC_Group, TC_Nullary, T_Condition, T_CoProcBody, T_CoProc, TC_Or, TC_Unary, T_DollarArithmetic, T_DollarBraceCommandExpansion, T_DollarBraced, T_DollarBracket, T_DollarDoubleQuoted, T_DollarExpansion, T_DoubleQuoted, T_Extglob, T_FdRedirect, T_ForArithmetic, T_ForIn, T_Function, T_HereDoc, T_HereString, T_IfExpression, T_Include, T_IndexedElement, T_IoDuplicate, T_IoFile, T_NormalWord, T_OrIf, T_Pipeline, T_ProcSub, T_Redirecting, T_Script, T_SelectIn, T_SimpleCommand, T_SourceCommand, T_Subshell, T_UntilExpression, T_WhileExpression #-}
|
||||||
|
|
||||||
instance Eq Token where
|
instance Eq Token where
|
||||||
OuterToken _ a == OuterToken _ b = a == b
|
OuterToken _ a == OuterToken _ b = a == b
|
||||||
|
|
|
@ -446,6 +446,12 @@ getLiteralStringExt more = g
|
||||||
-- Is this token a string literal?
|
-- Is this token a string literal?
|
||||||
isLiteral t = isJust $ getLiteralString t
|
isLiteral t = isJust $ getLiteralString t
|
||||||
|
|
||||||
|
-- Is this token a string literal number?
|
||||||
|
isLiteralNumber t = fromMaybe False $ do
|
||||||
|
s <- getLiteralString t
|
||||||
|
guard $ all isDigit s
|
||||||
|
return True
|
||||||
|
|
||||||
-- Escape user data for messages.
|
-- Escape user data for messages.
|
||||||
-- Messages generally avoid repeating user data, but sometimes it's helpful.
|
-- Messages generally avoid repeating user data, but sometimes it's helpful.
|
||||||
e4m = escapeForMessage
|
e4m = escapeForMessage
|
||||||
|
|
|
@ -103,8 +103,7 @@ nodeChecksToTreeCheck checkList =
|
||||||
|
|
||||||
nodeChecks :: [Parameters -> Token -> Writer [TokenComment] ()]
|
nodeChecks :: [Parameters -> Token -> Writer [TokenComment] ()]
|
||||||
nodeChecks = [
|
nodeChecks = [
|
||||||
checkUuoc
|
checkPipePitfalls
|
||||||
,checkPipePitfalls
|
|
||||||
,checkForInQuoted
|
,checkForInQuoted
|
||||||
,checkForInLs
|
,checkForInLs
|
||||||
,checkShorthandIf
|
,checkShorthandIf
|
||||||
|
@ -124,6 +123,7 @@ nodeChecks = [
|
||||||
,checkCaseAgainstGlob
|
,checkCaseAgainstGlob
|
||||||
,checkCommarrays
|
,checkCommarrays
|
||||||
,checkOrNeq
|
,checkOrNeq
|
||||||
|
,checkAndEq
|
||||||
,checkEchoWc
|
,checkEchoWc
|
||||||
,checkConstantIfs
|
,checkConstantIfs
|
||||||
,checkPipedAssignment
|
,checkPipedAssignment
|
||||||
|
@ -204,6 +204,8 @@ nodeChecks = [
|
||||||
,checkUnnecessaryArithmeticExpansionIndex
|
,checkUnnecessaryArithmeticExpansionIndex
|
||||||
,checkUnnecessaryParens
|
,checkUnnecessaryParens
|
||||||
,checkPlusEqualsNumber
|
,checkPlusEqualsNumber
|
||||||
|
,checkExpansionWithRedirection
|
||||||
|
,checkUnaryTestA
|
||||||
]
|
]
|
||||||
|
|
||||||
optionalChecks = map fst optionalTreeChecks
|
optionalChecks = map fst optionalTreeChecks
|
||||||
|
@ -272,6 +274,13 @@ optionalTreeChecks = [
|
||||||
cdPositive = "rm -r \"$(get_chroot_dir)/home\"",
|
cdPositive = "rm -r \"$(get_chroot_dir)/home\"",
|
||||||
cdNegative = "set -e; dir=\"$(get_chroot_dir)\"; rm -r \"$dir/home\""
|
cdNegative = "set -e; dir=\"$(get_chroot_dir)\"; rm -r \"$dir/home\""
|
||||||
}, checkExtraMaskedReturns)
|
}, checkExtraMaskedReturns)
|
||||||
|
|
||||||
|
,(newCheckDescription {
|
||||||
|
cdName = "useless-use-of-cat",
|
||||||
|
cdDescription = "Check for Useless Use Of Cat (UUOC)",
|
||||||
|
cdPositive = "cat foo | grep bar",
|
||||||
|
cdNegative = "grep bar foo"
|
||||||
|
}, nodeChecksToTreeCheck [checkUuoc])
|
||||||
]
|
]
|
||||||
|
|
||||||
optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment])
|
optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment])
|
||||||
|
@ -490,15 +499,12 @@ checkWrongArithmeticAssignment params (T_SimpleCommand id [T_Assignment _ _ _ _
|
||||||
sequence_ $ do
|
sequence_ $ do
|
||||||
str <- getNormalString val
|
str <- getNormalString val
|
||||||
var:op:_ <- matchRegex regex str
|
var:op:_ <- matchRegex regex str
|
||||||
Map.lookup var references
|
guard $ S.member var references
|
||||||
return . warn (getId val) 2100 $
|
return . warn (getId val) 2100 $
|
||||||
"Use $((..)) for arithmetics, e.g. i=$((i " ++ op ++ " 2))"
|
"Use $((..)) for arithmetics, e.g. i=$((i " ++ op ++ " 2))"
|
||||||
where
|
where
|
||||||
regex = mkRegex "^([_a-zA-Z][_a-zA-Z0-9]*)([+*-]).+$"
|
regex = mkRegex "^([_a-zA-Z][_a-zA-Z0-9]*)([+*-]).+$"
|
||||||
references = foldl (flip ($)) Map.empty (map insertRef $ variableFlow params)
|
references = S.fromList [name | Assignment (_, _, name, _) <- variableFlow params]
|
||||||
insertRef (Assignment (_, _, name, _)) =
|
|
||||||
Map.insert name ()
|
|
||||||
insertRef _ = Prelude.id
|
|
||||||
|
|
||||||
getNormalString (T_NormalWord _ words) = do
|
getNormalString (T_NormalWord _ words) = do
|
||||||
parts <- mapM getLiterals words
|
parts <- mapM getLiterals words
|
||||||
|
@ -876,13 +882,16 @@ prop_checkShorthandIf5 = verifyNot checkShorthandIf "foo && rm || printf b"
|
||||||
prop_checkShorthandIf6 = verifyNot checkShorthandIf "if foo && bar || baz; then true; fi"
|
prop_checkShorthandIf6 = verifyNot checkShorthandIf "if foo && bar || baz; then true; fi"
|
||||||
prop_checkShorthandIf7 = verifyNot checkShorthandIf "while foo && bar || baz; do true; done"
|
prop_checkShorthandIf7 = verifyNot checkShorthandIf "while foo && bar || baz; do true; done"
|
||||||
prop_checkShorthandIf8 = verify checkShorthandIf "if true; then foo && bar || baz; fi"
|
prop_checkShorthandIf8 = verify checkShorthandIf "if true; then foo && bar || baz; fi"
|
||||||
checkShorthandIf params x@(T_OrIf _ (T_AndIf id _ _) (T_Pipeline _ _ t))
|
prop_checkShorthandIf9 = verifyNot checkShorthandIf "foo && [ -x /file ] || bar"
|
||||||
| not (isOk t || inCondition) =
|
prop_checkShorthandIf10 = verifyNot checkShorthandIf "foo && bar || true"
|
||||||
|
prop_checkShorthandIf11 = verify checkShorthandIf "foo && bar || false"
|
||||||
|
checkShorthandIf params x@(T_OrIf _ (T_AndIf id _ b) (T_Pipeline _ _ t))
|
||||||
|
| not (isOk t || inCondition) && not (isTestCommand b) =
|
||||||
info id 2015 "Note that A && B || C is not if-then-else. C may run when A is true."
|
info id 2015 "Note that A && B || C is not if-then-else. C may run when A is true."
|
||||||
where
|
where
|
||||||
isOk [t] = isAssignment t || fromMaybe False (do
|
isOk [t] = isAssignment t || fromMaybe False (do
|
||||||
name <- getCommandBasename t
|
name <- getCommandBasename t
|
||||||
return $ name `elem` ["echo", "exit", "return", "printf"])
|
return $ name `elem` ["echo", "exit", "return", "printf", "true", ":"])
|
||||||
isOk _ = False
|
isOk _ = False
|
||||||
inCondition = isCondition $ getPath (parentMap params) x
|
inCondition = isCondition $ getPath (parentMap params) x
|
||||||
checkShorthandIf _ _ = return ()
|
checkShorthandIf _ _ = return ()
|
||||||
|
@ -972,32 +981,32 @@ prop_checkArrayWithoutIndex9 = verifyTree checkArrayWithoutIndex "read -r -a arr
|
||||||
prop_checkArrayWithoutIndex10 = verifyTree checkArrayWithoutIndex "read -ra arr <<< 'foo bar'; echo \"$arr\""
|
prop_checkArrayWithoutIndex10 = verifyTree checkArrayWithoutIndex "read -ra arr <<< 'foo bar'; echo \"$arr\""
|
||||||
prop_checkArrayWithoutIndex11 = verifyNotTree checkArrayWithoutIndex "read -rpfoobar r; r=42"
|
prop_checkArrayWithoutIndex11 = verifyNotTree checkArrayWithoutIndex "read -rpfoobar r; r=42"
|
||||||
checkArrayWithoutIndex params _ =
|
checkArrayWithoutIndex params _ =
|
||||||
doVariableFlowAnalysis readF writeF defaultMap (variableFlow params)
|
doVariableFlowAnalysis readF writeF defaultSet (variableFlow params)
|
||||||
where
|
where
|
||||||
defaultMap = Map.fromList $ map (\x -> (x,())) arrayVariables
|
defaultSet = S.fromList arrayVariables
|
||||||
readF _ (T_DollarBraced id _ token) _ = do
|
readF _ (T_DollarBraced id _ token) _ = do
|
||||||
map <- get
|
s <- get
|
||||||
return . maybeToList $ do
|
return . maybeToList $ do
|
||||||
name <- getLiteralString token
|
name <- getLiteralString token
|
||||||
assigned <- Map.lookup name map
|
guard $ S.member name s
|
||||||
return $ makeComment WarningC id 2128
|
return $ makeComment WarningC id 2128
|
||||||
"Expanding an array without an index only gives the first element."
|
"Expanding an array without an index only gives the first element."
|
||||||
readF _ _ _ = return []
|
readF _ _ _ = return []
|
||||||
|
|
||||||
writeF _ (T_Assignment id mode name [] _) _ (DataString _) = do
|
writeF _ (T_Assignment id mode name [] _) _ (DataString _) = do
|
||||||
isArray <- gets (Map.member name)
|
isArray <- gets (S.member name)
|
||||||
return $ if not isArray then [] else
|
return $ if not isArray then [] else
|
||||||
case mode of
|
case mode of
|
||||||
Assign -> [makeComment WarningC id 2178 "Variable was used as an array but is now assigned a string."]
|
Assign -> [makeComment WarningC id 2178 "Variable was used as an array but is now assigned a string."]
|
||||||
Append -> [makeComment WarningC id 2179 "Use array+=(\"item\") to append items to an array."]
|
Append -> [makeComment WarningC id 2179 "Use array+=(\"item\") to append items to an array."]
|
||||||
|
|
||||||
writeF _ t name (DataArray _) = do
|
writeF _ t name (DataArray _) = do
|
||||||
modify (Map.insert name ())
|
modify (S.insert name)
|
||||||
return []
|
return []
|
||||||
writeF _ expr name _ = do
|
writeF _ expr name _ = do
|
||||||
if isIndexed expr
|
if isIndexed expr
|
||||||
then modify (Map.insert name ())
|
then modify (S.insert name)
|
||||||
else modify (Map.delete name)
|
else modify (S.delete name)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
isIndexed expr =
|
isIndexed expr =
|
||||||
|
@ -1098,6 +1107,7 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) =
|
||||||
,"sudo" -- covering "sudo sh" and such
|
,"sudo" -- covering "sudo sh" and such
|
||||||
,"docker" -- like above
|
,"docker" -- like above
|
||||||
,"podman"
|
,"podman"
|
||||||
|
,"oc"
|
||||||
,"dpkg-query"
|
,"dpkg-query"
|
||||||
,"jq" -- could also check that user provides --arg
|
,"jq" -- could also check that user provides --arg
|
||||||
,"rename"
|
,"rename"
|
||||||
|
@ -1523,6 +1533,7 @@ prop_checkComparisonAgainstGlob3 = verify checkComparisonAgainstGlob "[ $cow = *
|
||||||
prop_checkComparisonAgainstGlob4 = verifyNot checkComparisonAgainstGlob "[ $cow = foo ]"
|
prop_checkComparisonAgainstGlob4 = verifyNot checkComparisonAgainstGlob "[ $cow = foo ]"
|
||||||
prop_checkComparisonAgainstGlob5 = verify checkComparisonAgainstGlob "[[ $cow != $bar ]]"
|
prop_checkComparisonAgainstGlob5 = verify checkComparisonAgainstGlob "[[ $cow != $bar ]]"
|
||||||
prop_checkComparisonAgainstGlob6 = verify checkComparisonAgainstGlob "[ $f != /* ]"
|
prop_checkComparisonAgainstGlob6 = verify checkComparisonAgainstGlob "[ $f != /* ]"
|
||||||
|
prop_checkComparisonAgainstGlob7 = verify checkComparisonAgainstGlob "#!/bin/busybox sh\n[[ $f == *foo* ]]"
|
||||||
checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op _ (T_NormalWord id [T_DollarBraced _ _ _]))
|
checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op _ (T_NormalWord id [T_DollarBraced _ _ _]))
|
||||||
| op `elem` ["=", "==", "!="] =
|
| op `elem` ["=", "==", "!="] =
|
||||||
warn id 2053 $ "Quote the right-hand side of " ++ op ++ " in [[ ]] to prevent glob matching."
|
warn id 2053 $ "Quote the right-hand side of " ++ op ++ " in [[ ]] to prevent glob matching."
|
||||||
|
@ -1530,10 +1541,14 @@ checkComparisonAgainstGlob params (TC_Binary _ SingleBracket op _ word)
|
||||||
| op `elem` ["=", "==", "!="] && isGlob word =
|
| op `elem` ["=", "==", "!="] && isGlob word =
|
||||||
err (getId word) 2081 msg
|
err (getId word) 2081 msg
|
||||||
where
|
where
|
||||||
msg = if isBashLike params
|
msg = if (shellType params) `elem` [Bash, Ksh] -- Busybox does not support glob matching
|
||||||
then "[ .. ] can't match globs. Use [[ .. ]] or case statement."
|
then "[ .. ] can't match globs. Use [[ .. ]] or case statement."
|
||||||
else "[ .. ] can't match globs. Use a case statement."
|
else "[ .. ] can't match globs. Use a case statement."
|
||||||
|
|
||||||
|
checkComparisonAgainstGlob params (TC_Binary _ DoubleBracket op _ word)
|
||||||
|
| shellType params == BusyboxSh && op `elem` ["=", "==", "!="] && isGlob word =
|
||||||
|
err (getId word) 2330 "BusyBox [[ .. ]] does not support glob matching. Use a case statement."
|
||||||
|
|
||||||
checkComparisonAgainstGlob _ _ = return ()
|
checkComparisonAgainstGlob _ _ = return ()
|
||||||
|
|
||||||
prop_checkCaseAgainstGlob1 = verify checkCaseAgainstGlob "case foo in lol$n) foo;; esac"
|
prop_checkCaseAgainstGlob1 = verify checkCaseAgainstGlob "case foo in lol$n) foo;; esac"
|
||||||
|
@ -1620,6 +1635,64 @@ checkOrNeq _ (T_OrIf id lhs rhs) = sequence_ $ do
|
||||||
checkOrNeq _ _ = return ()
|
checkOrNeq _ _ = return ()
|
||||||
|
|
||||||
|
|
||||||
|
prop_checkAndEq1 = verifyNot checkAndEq "cow=0; foo=0; if [[ $lol -eq cow && $lol -eq foo ]]; then echo foo; fi"
|
||||||
|
prop_checkAndEq2 = verifyNot checkAndEq "lol=0 foo=0; (( a==lol && a==foo ))"
|
||||||
|
prop_checkAndEq3 = verify checkAndEq "[ \"$a\" = lol && \"$a\" = foo ]"
|
||||||
|
prop_checkAndEq4 = verifyNot checkAndEq "[ a = $cow && b = $foo ]"
|
||||||
|
prop_checkAndEq5 = verifyNot checkAndEq "[[ $a = /home && $a = */public_html/* ]]"
|
||||||
|
prop_checkAndEq6 = verify checkAndEq "[ $a = a ] && [ $a = b ]"
|
||||||
|
prop_checkAndEq7 = verify checkAndEq "[ $a = a ] && [ $a = b ] || true"
|
||||||
|
prop_checkAndEq8 = verifyNot checkAndEq "[[ $a == x && $a == x ]]"
|
||||||
|
prop_checkAndEq9 = verifyNot checkAndEq "[ 0 -eq $FOO ] && [ 0 -eq $BAR ]"
|
||||||
|
prop_checkAndEq10 = verify checkAndEq "(( a == 1 && a == 2 ))"
|
||||||
|
prop_checkAndEq11 = verify checkAndEq "[ $x -eq 1 ] && [ $x -eq 2 ]"
|
||||||
|
prop_checkAndEq12 = verify checkAndEq "[ 1 -eq $x ] && [ $x -eq 2 ]"
|
||||||
|
prop_checkAndEq13 = verifyNot checkAndEq "[ 1 -eq $x ] && [ $x -eq 1 ]"
|
||||||
|
prop_checkAndEq14 = verifyNot checkAndEq "[ $a = $b ] && [ $a = $c ]"
|
||||||
|
|
||||||
|
checkAndEqOperands "-eq" rhs1 rhs2 = isLiteralNumber rhs1 && isLiteralNumber rhs2
|
||||||
|
checkAndEqOperands op rhs1 rhs2 | op == "=" || op == "==" = isLiteral rhs1 && isLiteral rhs2
|
||||||
|
checkAndEqOperands _ _ _ = False
|
||||||
|
|
||||||
|
-- For test-level "and": [ x = y -a x = z ]
|
||||||
|
checkAndEq _ (TC_And id typ op (TC_Binary _ _ op1 lhs1 rhs1 ) (TC_Binary _ _ op2 lhs2 rhs2))
|
||||||
|
| op1 == op2 && lhs1 == lhs2 && rhs1 /= rhs2 && checkAndEqOperands op1 rhs1 rhs2 =
|
||||||
|
warn id 2333 $ "You probably wanted " ++ (if typ == SingleBracket then "-o" else "||") ++ " here, otherwise it's always false."
|
||||||
|
|
||||||
|
-- For arithmetic context "and"
|
||||||
|
checkAndEq _ (TA_Binary id "&&" (TA_Binary _ "==" lhs1 rhs1) (TA_Binary _ "==" lhs2 rhs2))
|
||||||
|
| lhs1 == lhs2 && isLiteralNumber rhs1 && isLiteralNumber rhs2 =
|
||||||
|
warn id 2334 "You probably wanted || here, otherwise it's always false."
|
||||||
|
|
||||||
|
-- For command level "and": [ x = y ] && [ x = z ]
|
||||||
|
checkAndEq _ (T_AndIf id lhs rhs) = sequence_ $ do
|
||||||
|
(lhs1, op1, rhs1) <- getExpr lhs
|
||||||
|
(lhs2, op2, rhs2) <- getExpr rhs
|
||||||
|
guard $ op1 == op2
|
||||||
|
guard $ lhs1 == lhs2 && rhs1 /= rhs2
|
||||||
|
guard $ checkAndEqOperands op1 rhs1 rhs2
|
||||||
|
return $ warn id 2333 "You probably wanted || here, otherwise it's always false."
|
||||||
|
where
|
||||||
|
getExpr x =
|
||||||
|
case x of
|
||||||
|
T_AndIf _ lhs _ -> getExpr lhs -- Fetches x and y in `T_AndIf x (T_AndIf y z)`
|
||||||
|
T_Pipeline _ _ [x] -> getExpr x
|
||||||
|
T_Redirecting _ _ c -> getExpr c
|
||||||
|
T_Condition _ _ c -> getExpr c
|
||||||
|
TC_Binary _ _ op lhs rhs -> orient (lhs, op, rhs)
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
-- Swap items so that the constant side is rhs (or Nothing if both/neither is constant)
|
||||||
|
orient (lhs, op, rhs) =
|
||||||
|
case (isConstant lhs, isConstant rhs) of
|
||||||
|
(True, False) -> return (rhs, op, lhs)
|
||||||
|
(False, True) -> return (lhs, op, rhs)
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
|
||||||
|
checkAndEq _ _ = return ()
|
||||||
|
|
||||||
|
|
||||||
prop_checkValidCondOps1 = verify checkValidCondOps "[[ a -xz b ]]"
|
prop_checkValidCondOps1 = verify checkValidCondOps "[[ a -xz b ]]"
|
||||||
prop_checkValidCondOps2 = verify checkValidCondOps "[ -M a ]"
|
prop_checkValidCondOps2 = verify checkValidCondOps "[ -M a ]"
|
||||||
prop_checkValidCondOps2a = verifyNot checkValidCondOps "[ 3 \\> 2 ]"
|
prop_checkValidCondOps2a = verifyNot checkValidCondOps "[ 3 \\> 2 ]"
|
||||||
|
@ -1885,7 +1958,9 @@ prop_checkSpuriousExec8 = verifyNot checkSpuriousExec "exec {origout}>&1- >tmp.l
|
||||||
prop_checkSpuriousExec9 = verify checkSpuriousExec "for file in rc.d/*; do exec \"$file\"; done"
|
prop_checkSpuriousExec9 = verify checkSpuriousExec "for file in rc.d/*; do exec \"$file\"; done"
|
||||||
prop_checkSpuriousExec10 = verifyNot checkSpuriousExec "exec file; r=$?; printf >&2 'failed\n'; return $r"
|
prop_checkSpuriousExec10 = verifyNot checkSpuriousExec "exec file; r=$?; printf >&2 'failed\n'; return $r"
|
||||||
prop_checkSpuriousExec11 = verifyNot checkSpuriousExec "exec file; :"
|
prop_checkSpuriousExec11 = verifyNot checkSpuriousExec "exec file; :"
|
||||||
checkSpuriousExec _ = doLists
|
prop_checkSpuriousExec12 = verifyNot checkSpuriousExec "#!/bin/bash\nshopt -s execfail; exec foo; exec bar; echo 'Error'; exit 1;"
|
||||||
|
prop_checkSpuriousExec13 = verify checkSpuriousExec "#!/bin/dash\nshopt -s execfail; exec foo; exec bar; echo 'Error'; exit 1;"
|
||||||
|
checkSpuriousExec params t = when (not $ hasExecfail params) $ doLists t
|
||||||
where
|
where
|
||||||
doLists (T_Script _ _ cmds) = doList cmds False
|
doLists (T_Script _ _ cmds) = doList cmds False
|
||||||
doLists (T_BraceGroup _ cmds) = doList cmds False
|
doLists (T_BraceGroup _ cmds) = doList cmds False
|
||||||
|
@ -2373,15 +2448,9 @@ prop_checkUnused51 = verifyTree checkUnusedAssignments "x[y[z=1]]=1; echo ${x[@]
|
||||||
checkUnusedAssignments params t = execWriter (mapM_ warnFor unused)
|
checkUnusedAssignments params t = execWriter (mapM_ warnFor unused)
|
||||||
where
|
where
|
||||||
flow = variableFlow params
|
flow = variableFlow params
|
||||||
references = foldl (flip ($)) defaultMap (map insertRef flow)
|
references = Map.union (Map.fromList [(stripSuffix name, ()) | Reference (base, token, name) <- flow]) defaultMap
|
||||||
insertRef (Reference (base, token, name)) =
|
|
||||||
Map.insert (stripSuffix name) ()
|
|
||||||
insertRef _ = id
|
|
||||||
|
|
||||||
assignments = foldl (flip ($)) Map.empty (map insertAssignment flow)
|
assignments = Map.fromList [(name, token) | Assignment (_, token, name, _) <- flow, isVariableName name]
|
||||||
insertAssignment (Assignment (_, token, name, _)) | isVariableName name =
|
|
||||||
Map.insert name token
|
|
||||||
insertAssignment _ = id
|
|
||||||
|
|
||||||
unused = Map.assocs $ Map.difference assignments references
|
unused = Map.assocs $ Map.difference assignments references
|
||||||
|
|
||||||
|
@ -2445,6 +2514,7 @@ prop_checkUnassignedReferences_minusZDefault = verifyNotTree checkUnassignedRefe
|
||||||
prop_checkUnassignedReferences50 = verifyNotTree checkUnassignedReferences "echo ${foo:+bar}"
|
prop_checkUnassignedReferences50 = verifyNotTree checkUnassignedReferences "echo ${foo:+bar}"
|
||||||
prop_checkUnassignedReferences51 = verifyNotTree checkUnassignedReferences "echo ${foo:+$foo}"
|
prop_checkUnassignedReferences51 = verifyNotTree checkUnassignedReferences "echo ${foo:+$foo}"
|
||||||
prop_checkUnassignedReferences52 = verifyNotTree checkUnassignedReferences "wait -p pid; echo $pid"
|
prop_checkUnassignedReferences52 = verifyNotTree checkUnassignedReferences "wait -p pid; echo $pid"
|
||||||
|
prop_checkUnassignedReferences53 = verifyTree checkUnassignedReferences "x=($foo)"
|
||||||
|
|
||||||
checkUnassignedReferences = checkUnassignedReferences' False
|
checkUnassignedReferences = checkUnassignedReferences' False
|
||||||
checkUnassignedReferences' includeGlobals params t = warnings
|
checkUnassignedReferences' includeGlobals params t = warnings
|
||||||
|
@ -2500,14 +2570,12 @@ checkUnassignedReferences' includeGlobals params t = warnings
|
||||||
|
|
||||||
warnings = execWriter . sequence $ mapMaybe warningFor unassigned
|
warnings = execWriter . sequence $ mapMaybe warningFor unassigned
|
||||||
|
|
||||||
-- Due to parsing, foo=( [bar]=baz ) parses 'bar' as a reference even for assoc arrays.
|
-- ${foo[bar baz]} may not be referencing bar/baz. Just skip these.
|
||||||
-- Similarly, ${foo[bar baz]} may not be referencing bar/baz. Just skip these.
|
|
||||||
-- We can also have ${foo:+$foo} should be treated like [[ -n $foo ]] && echo $foo
|
-- We can also have ${foo:+$foo} should be treated like [[ -n $foo ]] && echo $foo
|
||||||
isException var t = any shouldExclude $ getPath (parentMap params) t
|
isException var t = any shouldExclude $ getPath (parentMap params) t
|
||||||
where
|
where
|
||||||
shouldExclude t =
|
shouldExclude t =
|
||||||
case t of
|
case t of
|
||||||
T_Array {} -> True
|
|
||||||
(T_DollarBraced _ _ l) ->
|
(T_DollarBraced _ _ l) ->
|
||||||
let str = concat $ oversimplify l
|
let str = concat $ oversimplify l
|
||||||
ref = getBracedReference str
|
ref = getBracedReference str
|
||||||
|
@ -2818,6 +2886,10 @@ prop_checkUnpassedInFunctions11 = verifyNotTree checkUnpassedInFunctions "foo()
|
||||||
prop_checkUnpassedInFunctions12 = verifyNotTree checkUnpassedInFunctions "foo() { echo ${!var*}; }; foo;"
|
prop_checkUnpassedInFunctions12 = verifyNotTree checkUnpassedInFunctions "foo() { echo ${!var*}; }; foo;"
|
||||||
prop_checkUnpassedInFunctions13 = verifyNotTree checkUnpassedInFunctions "# shellcheck disable=SC2120\nfoo() { echo $1; }\nfoo\n"
|
prop_checkUnpassedInFunctions13 = verifyNotTree checkUnpassedInFunctions "# shellcheck disable=SC2120\nfoo() { echo $1; }\nfoo\n"
|
||||||
prop_checkUnpassedInFunctions14 = verifyTree checkUnpassedInFunctions "foo() { echo $#; }; foo"
|
prop_checkUnpassedInFunctions14 = verifyTree checkUnpassedInFunctions "foo() { echo $#; }; foo"
|
||||||
|
prop_checkUnpassedInFunctions15 = verifyNotTree checkUnpassedInFunctions "foo() { echo ${1-x}; }; foo"
|
||||||
|
prop_checkUnpassedInFunctions16 = verifyNotTree checkUnpassedInFunctions "foo() { echo ${1:-x}; }; foo"
|
||||||
|
prop_checkUnpassedInFunctions17 = verifyNotTree checkUnpassedInFunctions "foo() { mycommand ${1+--verbose}; }; foo"
|
||||||
|
prop_checkUnpassedInFunctions18 = verifyNotTree checkUnpassedInFunctions "foo() { if mycheck; then foo ${1?Missing}; fi; }; foo"
|
||||||
checkUnpassedInFunctions params root =
|
checkUnpassedInFunctions params root =
|
||||||
execWriter $ mapM_ warnForGroup referenceGroups
|
execWriter $ mapM_ warnForGroup referenceGroups
|
||||||
where
|
where
|
||||||
|
@ -2834,9 +2906,10 @@ checkUnpassedInFunctions params root =
|
||||||
case x of
|
case x of
|
||||||
Assignment (_, _, str, _) -> isPositional str
|
Assignment (_, _, str, _) -> isPositional str
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
isPositionalReference function x =
|
isPositionalReference function x =
|
||||||
case x of
|
case x of
|
||||||
Reference (_, t, str) -> isPositional str && t `isDirectChildOf` function
|
Reference (_, t, str) -> isPositional str && t `isDirectChildOf` function && not (hasDefaultValue t)
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
isDirectChildOf child parent = fromMaybe False $ do
|
isDirectChildOf child parent = fromMaybe False $ do
|
||||||
|
@ -2850,6 +2923,7 @@ checkUnpassedInFunctions params root =
|
||||||
referenceList :: [(String, Bool, Token)]
|
referenceList :: [(String, Bool, Token)]
|
||||||
referenceList = execWriter $
|
referenceList = execWriter $
|
||||||
doAnalysis (sequence_ . checkCommand) root
|
doAnalysis (sequence_ . checkCommand) root
|
||||||
|
|
||||||
checkCommand :: Token -> Maybe (Writer [(String, Bool, Token)] ())
|
checkCommand :: Token -> Maybe (Writer [(String, Bool, Token)] ())
|
||||||
checkCommand t@(T_SimpleCommand _ _ (cmd:args)) = do
|
checkCommand t@(T_SimpleCommand _ _ (cmd:args)) = do
|
||||||
str <- getLiteralString cmd
|
str <- getLiteralString cmd
|
||||||
|
@ -2860,6 +2934,22 @@ checkUnpassedInFunctions params root =
|
||||||
isPositional str = str == "*" || str == "@" || str == "#"
|
isPositional str = str == "*" || str == "@" || str == "#"
|
||||||
|| (all isDigit str && str /= "0" && str /= "")
|
|| (all isDigit str && str /= "0" && str /= "")
|
||||||
|
|
||||||
|
-- True if t is a variable that specifies a default value,
|
||||||
|
-- such as ${1-x} or ${1:-x}.
|
||||||
|
hasDefaultValue t =
|
||||||
|
case t of
|
||||||
|
T_DollarBraced _ True l ->
|
||||||
|
let str = concat $ oversimplify l
|
||||||
|
in isDefaultValueModifier $ getBracedModifier str
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
isDefaultValueModifier str =
|
||||||
|
case str of
|
||||||
|
':':c:_ -> c `elem` handlesDefault
|
||||||
|
c:_ -> c `elem` handlesDefault
|
||||||
|
_ -> False
|
||||||
|
where handlesDefault = "-+?"
|
||||||
|
|
||||||
isArgumentless (_, b, _) = b
|
isArgumentless (_, b, _) = b
|
||||||
referenceGroups = Map.elems $ foldr updateWith Map.empty referenceList
|
referenceGroups = Map.elems $ foldr updateWith Map.empty referenceList
|
||||||
updateWith x@(name, _, _) = Map.insertWith (++) name [x]
|
updateWith x@(name, _, _) = Map.insertWith (++) name [x]
|
||||||
|
@ -3297,7 +3387,7 @@ checkReturnAgainstZero params token =
|
||||||
next@(TA_Unary _ "!" _):_ -> isOnlyTestInCommand next
|
next@(TA_Unary _ "!" _):_ -> isOnlyTestInCommand next
|
||||||
next@(TC_Group {}):_ -> isOnlyTestInCommand next
|
next@(TC_Group {}):_ -> isOnlyTestInCommand next
|
||||||
next@(TA_Sequence _ [_]):_ -> isOnlyTestInCommand next
|
next@(TA_Sequence _ [_]):_ -> isOnlyTestInCommand next
|
||||||
next@(TA_Parentesis _ _):_ -> isOnlyTestInCommand next
|
next@(TA_Parenthesis _ _):_ -> isOnlyTestInCommand next
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
-- TODO: Do better $? tracking and filter on whether
|
-- TODO: Do better $? tracking and filter on whether
|
||||||
|
@ -3592,6 +3682,8 @@ prop_checkPipeToNowhere17 = verify checkPipeToNowhere "echo World | cat << 'EOF'
|
||||||
prop_checkPipeToNowhere18 = verifyNot checkPipeToNowhere "ls 1>&3 3>&1 3>&- | wc -l"
|
prop_checkPipeToNowhere18 = verifyNot checkPipeToNowhere "ls 1>&3 3>&1 3>&- | wc -l"
|
||||||
prop_checkPipeToNowhere19 = verifyNot checkPipeToNowhere "find . -print0 | du --files0-from=/dev/stdin"
|
prop_checkPipeToNowhere19 = verifyNot checkPipeToNowhere "find . -print0 | du --files0-from=/dev/stdin"
|
||||||
prop_checkPipeToNowhere20 = verifyNot checkPipeToNowhere "find . | du --exclude-from=/dev/fd/0"
|
prop_checkPipeToNowhere20 = verifyNot checkPipeToNowhere "find . | du --exclude-from=/dev/fd/0"
|
||||||
|
prop_checkPipeToNowhere21 = verifyNot checkPipeToNowhere "yes | cp -ri foo/* bar"
|
||||||
|
prop_checkPipeToNowhere22 = verifyNot checkPipeToNowhere "yes | rm --interactive *"
|
||||||
|
|
||||||
data PipeType = StdoutPipe | StdoutStderrPipe | NoPipe deriving (Eq)
|
data PipeType = StdoutPipe | StdoutStderrPipe | NoPipe deriving (Eq)
|
||||||
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
|
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
|
||||||
|
@ -3657,6 +3749,7 @@ checkPipeToNowhere params t =
|
||||||
commandSpecificException name cmd =
|
commandSpecificException name cmd =
|
||||||
case name of
|
case name of
|
||||||
"du" -> any ((`elem` ["exclude-from", "files0-from"]) . snd) $ getAllFlags cmd
|
"du" -> any ((`elem` ["exclude-from", "files0-from"]) . snd) $ getAllFlags cmd
|
||||||
|
_ | name `elem` interactiveFlagCmds -> hasInteractiveFlag cmd
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
warnAboutDupes (n, list@(_:_:_)) =
|
warnAboutDupes (n, list@(_:_:_)) =
|
||||||
|
@ -3680,7 +3773,7 @@ checkPipeToNowhere params t =
|
||||||
name <- getCommandBasename cmd
|
name <- getCommandBasename cmd
|
||||||
guard $ name `elem` nonReadingCommands
|
guard $ name `elem` nonReadingCommands
|
||||||
guard . not $ hasAdditionalConsumers cmd
|
guard . not $ hasAdditionalConsumers cmd
|
||||||
guard . not $ name `elem` ["cp", "mv", "rm"] && cmd `hasFlag` "i"
|
guard . not $ name `elem` interactiveFlagCmds && hasInteractiveFlag cmd
|
||||||
let suggestion =
|
let suggestion =
|
||||||
if name == "echo"
|
if name == "echo"
|
||||||
then "Did you want 'cat' instead?"
|
then "Did you want 'cat' instead?"
|
||||||
|
@ -3695,6 +3788,9 @@ checkPipeToNowhere params t =
|
||||||
treeContains pred t = isNothing $
|
treeContains pred t = isNothing $
|
||||||
doAnalysis (guard . not . pred) t
|
doAnalysis (guard . not . pred) t
|
||||||
|
|
||||||
|
interactiveFlagCmds = [ "cp", "mv", "rm" ]
|
||||||
|
hasInteractiveFlag cmd = cmd `hasFlag` "i" || cmd `hasFlag` "interactive"
|
||||||
|
|
||||||
mayConsume t =
|
mayConsume t =
|
||||||
case t of
|
case t of
|
||||||
T_ProcSub _ "<" _ -> True
|
T_ProcSub _ "<" _ -> True
|
||||||
|
@ -3761,32 +3857,32 @@ prop_checkUseBeforeDefinition1 = verifyTree checkUseBeforeDefinition "f; f() { t
|
||||||
prop_checkUseBeforeDefinition2 = verifyNotTree checkUseBeforeDefinition "f() { true; }; f"
|
prop_checkUseBeforeDefinition2 = verifyNotTree checkUseBeforeDefinition "f() { true; }; f"
|
||||||
prop_checkUseBeforeDefinition3 = verifyNotTree checkUseBeforeDefinition "if ! mycmd --version; then mycmd() { true; }; fi"
|
prop_checkUseBeforeDefinition3 = verifyNotTree checkUseBeforeDefinition "if ! mycmd --version; then mycmd() { true; }; fi"
|
||||||
prop_checkUseBeforeDefinition4 = verifyNotTree checkUseBeforeDefinition "mycmd || mycmd() { f; }"
|
prop_checkUseBeforeDefinition4 = verifyNotTree checkUseBeforeDefinition "mycmd || mycmd() { f; }"
|
||||||
checkUseBeforeDefinition _ t =
|
prop_checkUseBeforeDefinition5 = verifyTree checkUseBeforeDefinition "false || mycmd; mycmd() { f; }"
|
||||||
execWriter $ evalStateT (mapM_ examine $ revCommands) Map.empty
|
prop_checkUseBeforeDefinition6 = verifyNotTree checkUseBeforeDefinition "f() { one; }; f; f() { two; }; f"
|
||||||
|
checkUseBeforeDefinition :: Parameters -> Token -> [TokenComment]
|
||||||
|
checkUseBeforeDefinition params t = fromMaybe [] $ do
|
||||||
|
cfga <- cfgAnalysis params
|
||||||
|
let funcs = execState (doAnalysis findFunction t) Map.empty
|
||||||
|
-- Green cut: no point enumerating commands if there are no functions.
|
||||||
|
guard . not $ Map.null funcs
|
||||||
|
return $ execWriter $ doAnalysis (findInvocation cfga funcs) t
|
||||||
where
|
where
|
||||||
examine t = case t of
|
findFunction t =
|
||||||
T_Pipeline _ _ [T_Redirecting _ _ (T_Function _ _ _ name _)] ->
|
case t of
|
||||||
modify $ Map.insert name t
|
T_Function id _ _ name _ -> modify (Map.insertWith (++) name [id])
|
||||||
T_Annotation _ _ w -> examine w
|
_ -> return ()
|
||||||
T_Pipeline _ _ cmds -> do
|
|
||||||
m <- get
|
|
||||||
unless (Map.null m) $
|
|
||||||
mapM_ (checkUsage m) $ concatMap recursiveSequences cmds
|
|
||||||
_ -> return ()
|
|
||||||
|
|
||||||
checkUsage map cmd = sequence_ $ do
|
findInvocation cfga funcs t =
|
||||||
name <- getCommandName cmd
|
case t of
|
||||||
def <- Map.lookup name map
|
T_SimpleCommand id _ (cmd:_) -> sequence_ $ do
|
||||||
return $
|
name <- getLiteralString cmd
|
||||||
err (getId cmd) 2218
|
invocations <- Map.lookup name funcs
|
||||||
"This function is only defined later. Move the definition up."
|
-- Is the function definitely being defined later?
|
||||||
|
guard $ any (\c -> CF.doesPostDominate cfga c id) invocations
|
||||||
revCommands = reverse $ concat $ getCommandSequences t
|
-- Was one already defined, so it's actually a re-definition?
|
||||||
recursiveSequences x =
|
guard . not $ any (\c -> CF.doesPostDominate cfga id c) invocations
|
||||||
let list = concat $ getCommandSequences x in
|
return $ err id 2218 "This function is only defined later. Move the definition up."
|
||||||
if null list
|
_ -> return ()
|
||||||
then [x]
|
|
||||||
else concatMap recursiveSequences list
|
|
||||||
|
|
||||||
prop_checkForLoopGlobVariables1 = verify checkForLoopGlobVariables "for i in $var/*.txt; do true; done"
|
prop_checkForLoopGlobVariables1 = verify checkForLoopGlobVariables "for i in $var/*.txt; do true; done"
|
||||||
prop_checkForLoopGlobVariables2 = verifyNot checkForLoopGlobVariables "for i in \"$var\"/*.txt; do true; done"
|
prop_checkForLoopGlobVariables2 = verifyNot checkForLoopGlobVariables "for i in \"$var\"/*.txt; do true; done"
|
||||||
|
@ -3962,13 +4058,10 @@ prop_checkTranslatedStringVariable4 = verifyNot checkTranslatedStringVariable "v
|
||||||
prop_checkTranslatedStringVariable5 = verifyNot checkTranslatedStringVariable "foo=var; bar=val2; $\"foo bar\""
|
prop_checkTranslatedStringVariable5 = verifyNot checkTranslatedStringVariable "foo=var; bar=val2; $\"foo bar\""
|
||||||
checkTranslatedStringVariable params (T_DollarDoubleQuoted id [T_Literal _ s])
|
checkTranslatedStringVariable params (T_DollarDoubleQuoted id [T_Literal _ s])
|
||||||
| all isVariableChar s
|
| all isVariableChar s
|
||||||
&& Map.member s assignments
|
&& S.member s assignments
|
||||||
= warnWithFix id 2256 "This translated string is the name of a variable. Flip leading $ and \" if this should be a quoted substitution." (fix id)
|
= warnWithFix id 2256 "This translated string is the name of a variable. Flip leading $ and \" if this should be a quoted substitution." (fix id)
|
||||||
where
|
where
|
||||||
assignments = foldl (flip ($)) Map.empty (map insertAssignment $ variableFlow params)
|
assignments = S.fromList [name | Assignment (_, _, name, _) <- variableFlow params, isVariableName name]
|
||||||
insertAssignment (Assignment (_, token, name, _)) | isVariableName name =
|
|
||||||
Map.insert name token
|
|
||||||
insertAssignment _ = Prelude.id
|
|
||||||
fix id = fixWith [replaceStart id params 2 "\"$"]
|
fix id = fixWith [replaceStart id params 2 "\"$"]
|
||||||
checkTranslatedStringVariable _ _ = return ()
|
checkTranslatedStringVariable _ _ = return ()
|
||||||
|
|
||||||
|
@ -3998,6 +4091,7 @@ prop_checkUselessBang6 = verify checkUselessBang "set -e; { ! true; }"
|
||||||
prop_checkUselessBang7 = verifyNot checkUselessBang "set -e; x() { ! [ x ]; }"
|
prop_checkUselessBang7 = verifyNot checkUselessBang "set -e; x() { ! [ x ]; }"
|
||||||
prop_checkUselessBang8 = verifyNot checkUselessBang "set -e; if { ! true; }; then true; fi"
|
prop_checkUselessBang8 = verifyNot checkUselessBang "set -e; if { ! true; }; then true; fi"
|
||||||
prop_checkUselessBang9 = verifyNot checkUselessBang "set -e; while ! true; do true; done"
|
prop_checkUselessBang9 = verifyNot checkUselessBang "set -e; while ! true; do true; done"
|
||||||
|
prop_checkUselessBang10 = verify checkUselessBang "set -e\nshellcheck disable=SC0000\n! true\nrest"
|
||||||
checkUselessBang params t = when (hasSetE params) $ mapM_ check (getNonReturningCommands t)
|
checkUselessBang params t = when (hasSetE params) $ mapM_ check (getNonReturningCommands t)
|
||||||
where
|
where
|
||||||
check t =
|
check t =
|
||||||
|
@ -4006,6 +4100,7 @@ checkUselessBang params t = when (hasSetE params) $ mapM_ check (getNonReturning
|
||||||
addComment $ makeCommentWithFix InfoC id 2251
|
addComment $ makeCommentWithFix InfoC id 2251
|
||||||
"This ! is not on a condition and skips errexit. Use `&& exit 1` instead, or make sure $? is checked."
|
"This ! is not on a condition and skips errexit. Use `&& exit 1` instead, or make sure $? is checked."
|
||||||
(fixWith [replaceStart id params 1 "", replaceEnd (getId cmd) params 0 " && exit 1"])
|
(fixWith [replaceStart id params 1 "", replaceEnd (getId cmd) params 0 " && exit 1"])
|
||||||
|
T_Annotation _ _ t -> check t
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
-- Get all the subcommands that aren't likely to be the return value
|
-- Get all the subcommands that aren't likely to be the return value
|
||||||
|
@ -4196,7 +4291,7 @@ checkBadTestAndOr params t =
|
||||||
in
|
in
|
||||||
mapM_ checkTest commandWithSeps
|
mapM_ checkTest commandWithSeps
|
||||||
checkTest (before, cmd, after) =
|
checkTest (before, cmd, after) =
|
||||||
when (isTest cmd) $ do
|
when (isTestCommand cmd) $ do
|
||||||
checkPipe before
|
checkPipe before
|
||||||
checkPipe after
|
checkPipe after
|
||||||
|
|
||||||
|
@ -4212,17 +4307,10 @@ checkBadTestAndOr params t =
|
||||||
T_AndIf _ _ rhs -> checkAnds id rhs
|
T_AndIf _ _ rhs -> checkAnds id rhs
|
||||||
T_OrIf _ _ rhs -> checkAnds id rhs
|
T_OrIf _ _ rhs -> checkAnds id rhs
|
||||||
T_Pipeline _ _ list | not (null list) -> checkAnds id (last list)
|
T_Pipeline _ _ list | not (null list) -> checkAnds id (last list)
|
||||||
cmd -> when (isTest cmd) $
|
cmd -> when (isTestCommand cmd) $
|
||||||
errWithFix id 2265 "Use && for logical AND. Single & will background and return true." $
|
errWithFix id 2265 "Use && for logical AND. Single & will background and return true." $
|
||||||
(fixWith [replaceEnd id params 0 "&"])
|
(fixWith [replaceEnd id params 0 "&"])
|
||||||
|
|
||||||
isTest t =
|
|
||||||
case t of
|
|
||||||
T_Condition {} -> True
|
|
||||||
T_SimpleCommand {} -> t `isCommand` "test"
|
|
||||||
T_Redirecting _ _ t -> isTest t
|
|
||||||
T_Annotation _ _ t -> isTest t
|
|
||||||
_ -> False
|
|
||||||
|
|
||||||
prop_checkComparisonWithLeadingX1 = verify checkComparisonWithLeadingX "[ x$foo = xlol ]"
|
prop_checkComparisonWithLeadingX1 = verify checkComparisonWithLeadingX "[ x$foo = xlol ]"
|
||||||
prop_checkComparisonWithLeadingX2 = verify checkComparisonWithLeadingX "test x$foo = xlol"
|
prop_checkComparisonWithLeadingX2 = verify checkComparisonWithLeadingX "test x$foo = xlol"
|
||||||
|
@ -4538,13 +4626,13 @@ prop_checkRequireDoubleBracket2 = verifyTree checkRequireDoubleBracket "[ foo -o
|
||||||
prop_checkRequireDoubleBracket3 = verifyNotTree checkRequireDoubleBracket "#!/bin/sh\n[ -x foo ]"
|
prop_checkRequireDoubleBracket3 = verifyNotTree checkRequireDoubleBracket "#!/bin/sh\n[ -x foo ]"
|
||||||
prop_checkRequireDoubleBracket4 = verifyNotTree checkRequireDoubleBracket "[[ -x foo ]]"
|
prop_checkRequireDoubleBracket4 = verifyNotTree checkRequireDoubleBracket "[[ -x foo ]]"
|
||||||
checkRequireDoubleBracket params =
|
checkRequireDoubleBracket params =
|
||||||
if isBashLike params
|
if (shellType params) `elem` [Bash, Ksh, BusyboxSh]
|
||||||
then nodeChecksToTreeCheck [check] params
|
then nodeChecksToTreeCheck [check] params
|
||||||
else const []
|
else const []
|
||||||
where
|
where
|
||||||
check _ t = case t of
|
check _ t = case t of
|
||||||
T_Condition id SingleBracket _ ->
|
T_Condition id SingleBracket _ ->
|
||||||
styleWithFix id 2292 "Prefer [[ ]] over [ ] for tests in Bash/Ksh." (fixFor t)
|
styleWithFix id 2292 "Prefer [[ ]] over [ ] for tests in Bash/Ksh/Busybox." (fixFor t)
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
fixFor t = fixWith $
|
fixFor t = fixWith $
|
||||||
|
@ -4895,16 +4983,33 @@ checkBatsTestDoesNotUseNegation params t =
|
||||||
prop_checkCommandIsUnreachable1 = verify checkCommandIsUnreachable "foo; bar; exit; baz"
|
prop_checkCommandIsUnreachable1 = verify checkCommandIsUnreachable "foo; bar; exit; baz"
|
||||||
prop_checkCommandIsUnreachable2 = verify checkCommandIsUnreachable "die() { exit; }; foo; bar; die; baz"
|
prop_checkCommandIsUnreachable2 = verify checkCommandIsUnreachable "die() { exit; }; foo; bar; die; baz"
|
||||||
prop_checkCommandIsUnreachable3 = verifyNot checkCommandIsUnreachable "foo; bar || exit; baz"
|
prop_checkCommandIsUnreachable3 = verifyNot checkCommandIsUnreachable "foo; bar || exit; baz"
|
||||||
|
prop_checkCommandIsUnreachable4 = verifyNot checkCommandIsUnreachable "f() { foo; }; # Maybe sourced"
|
||||||
|
prop_checkCommandIsUnreachable5 = verify checkCommandIsUnreachable "f() { foo; }; exit # Not sourced"
|
||||||
checkCommandIsUnreachable params t =
|
checkCommandIsUnreachable params t =
|
||||||
case t of
|
case t of
|
||||||
T_Pipeline {} -> sequence_ $ do
|
T_Pipeline {} -> sequence_ $ do
|
||||||
cfga <- cfgAnalysis params
|
cfga <- cfgAnalysis params
|
||||||
state <- CF.getIncomingState cfga id
|
state <- CF.getIncomingState cfga (getId t)
|
||||||
guard . not $ CF.stateIsReachable state
|
guard . not $ CF.stateIsReachable state
|
||||||
guard . not $ isSourced params t
|
guard . not $ isSourced params t
|
||||||
return $ info id 2317 "Command appears to be unreachable. Check usage (or ignore if invoked indirectly)."
|
guard . not $ any (\t -> isUnreachable t || isUnreachableFunction t) $ NE.drop 1 $ getPath (parentMap params) t
|
||||||
|
return $ info (getId t) 2317 "Command appears to be unreachable. Check usage (or ignore if invoked indirectly)."
|
||||||
|
T_Function id _ _ _ _ ->
|
||||||
|
when (isUnreachableFunction t
|
||||||
|
&& (not . any isUnreachableFunction . NE.drop 1 $ getPath (parentMap params) t)
|
||||||
|
&& (not $ isSourced params t)) $
|
||||||
|
info id 2329 "This function is never invoked. Check usage (or ignored if invoked indirectly)."
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
where id = getId t
|
where
|
||||||
|
isUnreachableFunction :: Token -> Bool
|
||||||
|
isUnreachableFunction f =
|
||||||
|
case f of
|
||||||
|
T_Function id _ _ _ t -> isUnreachable t
|
||||||
|
_ -> False
|
||||||
|
isUnreachable t = fromMaybe False $ do
|
||||||
|
cfga <- cfgAnalysis params
|
||||||
|
state <- CF.getIncomingState cfga (getId t)
|
||||||
|
return . not $ CF.stateIsReachable state
|
||||||
|
|
||||||
|
|
||||||
prop_checkOverwrittenExitCode1 = verify checkOverwrittenExitCode "x; [ $? -eq 1 ] || [ $? -eq 2 ]"
|
prop_checkOverwrittenExitCode1 = verify checkOverwrittenExitCode "x; [ $? -eq 1 ] || [ $? -eq 2 ]"
|
||||||
|
@ -4984,14 +5089,14 @@ checkUnnecessaryParens params t =
|
||||||
T_ForArithmetic _ x y z _ -> mapM_ (checkLeading "for (((x); (y); (z))) is the same as for ((x; y; z))") [x,y,z]
|
T_ForArithmetic _ x y z _ -> mapM_ (checkLeading "for (((x); (y); (z))) is the same as for ((x; y; z))") [x,y,z]
|
||||||
T_Assignment _ _ _ [t] _ -> checkLeading "a[(x)] is the same as a[x]" t
|
T_Assignment _ _ _ [t] _ -> checkLeading "a[(x)] is the same as a[x]" t
|
||||||
T_Arithmetic _ t -> checkLeading "(( (x) )) is the same as (( x ))" t
|
T_Arithmetic _ t -> checkLeading "(( (x) )) is the same as (( x ))" t
|
||||||
TA_Parentesis _ (TA_Sequence _ [ TA_Parentesis id _ ]) ->
|
TA_Parenthesis _ (TA_Sequence _ [ TA_Parenthesis id _ ]) ->
|
||||||
styleWithFix id 2322 "In arithmetic contexts, ((x)) is the same as (x). Prefer only one layer of parentheses." $ fix id
|
styleWithFix id 2322 "In arithmetic contexts, ((x)) is the same as (x). Prefer only one layer of parentheses." $ fix id
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
where
|
where
|
||||||
|
|
||||||
checkLeading str t =
|
checkLeading str t =
|
||||||
case t of
|
case t of
|
||||||
TA_Sequence _ [TA_Parentesis id _ ] -> styleWithFix id 2323 (str ++ ". Prefer not wrapping in additional parentheses.") $ fix id
|
TA_Sequence _ [TA_Parenthesis id _ ] -> styleWithFix id 2323 (str ++ ". Prefer not wrapping in additional parentheses.") $ fix id
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
fix id =
|
fix id =
|
||||||
|
@ -5017,7 +5122,8 @@ checkPlusEqualsNumber params t =
|
||||||
state <- CF.getIncomingState cfga id
|
state <- CF.getIncomingState cfga id
|
||||||
guard $ isNumber state word
|
guard $ isNumber state word
|
||||||
guard . not $ fromMaybe False $ CF.variableMayBeDeclaredInteger state var
|
guard . not $ fromMaybe False $ CF.variableMayBeDeclaredInteger state var
|
||||||
return $ warn id 2324 "var+=1 will append, not increment. Use (( var += 1 )), declare -i var, or quote number to silence."
|
-- Recommend "typeset" because ksh does not have "declare".
|
||||||
|
return $ warn id 2324 "var+=1 will append, not increment. Use (( var += 1 )), typeset -i var, or quote number to silence."
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
where
|
where
|
||||||
|
@ -5039,5 +5145,52 @@ checkPlusEqualsNumber params t =
|
||||||
isUnquotedNumber || isNumericalVariableName || isNumericalVariableExpansion
|
isUnquotedNumber || isNumericalVariableName || isNumericalVariableExpansion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
prop_checkExpansionWithRedirection1 = verify checkExpansionWithRedirection "var=$(foo > bar)"
|
||||||
|
prop_checkExpansionWithRedirection2 = verify checkExpansionWithRedirection "var=`foo 1> bar`"
|
||||||
|
prop_checkExpansionWithRedirection3 = verify checkExpansionWithRedirection "var=${ foo >> bar; }"
|
||||||
|
prop_checkExpansionWithRedirection4 = verify checkExpansionWithRedirection "var=$(foo | bar > baz)"
|
||||||
|
prop_checkExpansionWithRedirection5 = verifyNot checkExpansionWithRedirection "stderr=$(foo 2>&1 > /dev/null)"
|
||||||
|
prop_checkExpansionWithRedirection6 = verifyNot checkExpansionWithRedirection "var=$(foo; bar > baz)"
|
||||||
|
prop_checkExpansionWithRedirection7 = verifyNot checkExpansionWithRedirection "var=$(foo > bar; baz)"
|
||||||
|
prop_checkExpansionWithRedirection8 = verifyNot checkExpansionWithRedirection "var=$(cat <&3)"
|
||||||
|
checkExpansionWithRedirection params t =
|
||||||
|
case t of
|
||||||
|
T_DollarExpansion id [cmd] -> check id cmd
|
||||||
|
T_Backticked id [cmd] -> check id cmd
|
||||||
|
T_DollarBraceCommandExpansion id [cmd] -> check id cmd
|
||||||
|
_ -> return ()
|
||||||
|
where
|
||||||
|
check id pipe =
|
||||||
|
case pipe of
|
||||||
|
(T_Pipeline _ _ t@(_:_)) -> checkCmd id (last t)
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
|
checkCmd captureId (T_Redirecting _ redirs _) = foldr (walk captureId) (return ()) redirs
|
||||||
|
|
||||||
|
walk captureId t acc =
|
||||||
|
case t of
|
||||||
|
T_FdRedirect _ _ (T_IoDuplicate _ _ "1") -> return ()
|
||||||
|
T_FdRedirect id "1" (T_IoDuplicate _ _ _) -> return ()
|
||||||
|
T_FdRedirect id "" (T_IoDuplicate _ op _) | op `elem` [T_GREATAND (Id 0), T_Greater (Id 0)] -> emit id captureId True
|
||||||
|
T_FdRedirect id str (T_IoFile _ op file) | str `elem` ["", "1"] && op `elem` [ T_DGREAT (Id 0), T_Greater (Id 0) ] ->
|
||||||
|
emit id captureId $ getLiteralString file /= Just "/dev/null"
|
||||||
|
_ -> acc
|
||||||
|
|
||||||
|
emit redirectId captureId suggestTee = do
|
||||||
|
warn captureId 2327 "This command substitution will be empty because the command's output gets redirected away."
|
||||||
|
err redirectId 2328 $ "This redirection takes output away from the command substitution" ++ if suggestTee then " (use tee to duplicate)." else "."
|
||||||
|
|
||||||
|
|
||||||
|
prop_checkUnaryTestA1 = verify checkUnaryTestA "[ -a foo ]"
|
||||||
|
prop_checkUnaryTestA2 = verify checkUnaryTestA "[ ! -a foo ]"
|
||||||
|
prop_checkUnaryTestA3 = verifyNot checkUnaryTestA "[ foo -a bar ]"
|
||||||
|
checkUnaryTestA params t =
|
||||||
|
case t of
|
||||||
|
TC_Unary id _ "-a" _ ->
|
||||||
|
styleWithFix id 2331 "For file existence, prefer standard -e over legacy -a." $
|
||||||
|
fixWith [replaceStart id params 2 "-e"]
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
return []
|
return []
|
||||||
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
||||||
|
|
|
@ -89,6 +89,8 @@ data Parameters = Parameters {
|
||||||
hasSetE :: Bool,
|
hasSetE :: Bool,
|
||||||
-- Whether this script has 'set -o pipefail' anywhere.
|
-- Whether this script has 'set -o pipefail' anywhere.
|
||||||
hasPipefail :: Bool,
|
hasPipefail :: Bool,
|
||||||
|
-- Whether this script has 'shopt -s execfail' anywhere.
|
||||||
|
hasExecfail :: Bool,
|
||||||
-- A linear (bad) analysis of data flow
|
-- A linear (bad) analysis of data flow
|
||||||
variableFlow :: [StackData],
|
variableFlow :: [StackData],
|
||||||
-- A map from Id to Token
|
-- A map from Id to Token
|
||||||
|
@ -226,6 +228,10 @@ makeParameters spec = params
|
||||||
BusyboxSh -> isOptionSet "pipefail" root
|
BusyboxSh -> isOptionSet "pipefail" root
|
||||||
Sh -> True
|
Sh -> True
|
||||||
Ksh -> isOptionSet "pipefail" root,
|
Ksh -> isOptionSet "pipefail" root,
|
||||||
|
hasExecfail =
|
||||||
|
case shellType params of
|
||||||
|
Bash -> isOptionSet "execfail" root
|
||||||
|
_ -> False,
|
||||||
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShell spec),
|
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShell spec),
|
||||||
idMap = getTokenMap root,
|
idMap = getTokenMap root,
|
||||||
parentMap = getParentTree root,
|
parentMap = getParentTree root,
|
||||||
|
@ -535,7 +541,9 @@ getModifiedVariables t =
|
||||||
T_BatsTest {} -> [
|
T_BatsTest {} -> [
|
||||||
(t, t, "lines", DataArray SourceExternal),
|
(t, t, "lines", DataArray SourceExternal),
|
||||||
(t, t, "status", DataString SourceInteger),
|
(t, t, "status", DataString SourceInteger),
|
||||||
(t, t, "output", DataString SourceExternal)
|
(t, t, "output", DataString SourceExternal),
|
||||||
|
(t, t, "stderr", DataString SourceExternal),
|
||||||
|
(t, t, "stderr_lines", DataArray SourceExternal)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- Count [[ -v foo ]] as an "assignment".
|
-- Count [[ -v foo ]] as an "assignment".
|
||||||
|
@ -557,8 +565,12 @@ getModifiedVariables t =
|
||||||
T_FdRedirect _ ('{':var) op -> -- {foo}>&2 modifies foo
|
T_FdRedirect _ ('{':var) op -> -- {foo}>&2 modifies foo
|
||||||
[(t, t, takeWhile (/= '}') var, DataString SourceInteger) | not $ isClosingFileOp op]
|
[(t, t, takeWhile (/= '}') var, DataString SourceInteger) | not $ isClosingFileOp op]
|
||||||
|
|
||||||
T_CoProc _ name _ ->
|
T_CoProc _ Nothing _ ->
|
||||||
[(t, t, fromMaybe "COPROC" name, DataArray SourceInteger)]
|
[(t, t, "COPROC", DataArray SourceInteger)]
|
||||||
|
|
||||||
|
T_CoProc _ (Just token) _ -> do
|
||||||
|
name <- maybeToList $ getLiteralString token
|
||||||
|
[(t, t, name, DataArray SourceInteger)]
|
||||||
|
|
||||||
--Points to 'for' rather than variable
|
--Points to 'for' rather than variable
|
||||||
T_ForIn id str [] _ -> [(t, t, str, DataString SourceExternal)]
|
T_ForIn id str [] _ -> [(t, t, str, DataString SourceExternal)]
|
||||||
|
@ -902,16 +914,6 @@ supportsArrays Bash = True
|
||||||
supportsArrays Ksh = True
|
supportsArrays Ksh = True
|
||||||
supportsArrays _ = False
|
supportsArrays _ = False
|
||||||
|
|
||||||
-- Returns true if the shell is Bash or Ksh (sorry for the name, Ksh)
|
|
||||||
isBashLike :: Parameters -> Bool
|
|
||||||
isBashLike params =
|
|
||||||
case shellType params of
|
|
||||||
Bash -> True
|
|
||||||
Ksh -> True
|
|
||||||
Dash -> False
|
|
||||||
BusyboxSh -> False
|
|
||||||
Sh -> False
|
|
||||||
|
|
||||||
isTrueAssignmentSource c =
|
isTrueAssignmentSource c =
|
||||||
case c of
|
case c of
|
||||||
DataString SourceChecked -> False
|
DataString SourceChecked -> False
|
||||||
|
@ -929,6 +931,14 @@ modifiesVariable params token name =
|
||||||
Assignment (_, _, n, source) -> isTrueAssignmentSource source && n == name
|
Assignment (_, _, n, source) -> isTrueAssignmentSource source && n == name
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
|
isTestCommand t =
|
||||||
|
case t of
|
||||||
|
T_Condition {} -> True
|
||||||
|
T_SimpleCommand {} -> t `isCommand` "test"
|
||||||
|
T_Redirecting _ _ t -> isTestCommand t
|
||||||
|
T_Annotation _ _ t -> isTestCommand t
|
||||||
|
T_Pipeline _ _ [t] -> isTestCommand t
|
||||||
|
_ -> False
|
||||||
|
|
||||||
return []
|
return []
|
||||||
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
||||||
|
|
|
@ -295,19 +295,19 @@ removeUnnecessaryStructuralNodes (nodes, edges, mapping, association) =
|
||||||
regularEdges = filter isRegularEdge edges
|
regularEdges = filter isRegularEdge edges
|
||||||
inDegree = counter $ map (\(from,to,_) -> from) regularEdges
|
inDegree = counter $ map (\(from,to,_) -> from) regularEdges
|
||||||
outDegree = counter $ map (\(from,to,_) -> to) regularEdges
|
outDegree = counter $ map (\(from,to,_) -> to) regularEdges
|
||||||
structuralNodes = S.fromList $ map fst $ filter isStructural nodes
|
structuralNodes = S.fromList [node | (node, CFStructuralNode) <- nodes]
|
||||||
candidateNodes = S.filter isLinear structuralNodes
|
candidateNodes = S.filter isLinear structuralNodes
|
||||||
edgesToCollapse = S.fromList $ filter filterEdges regularEdges
|
edgesToCollapse = S.fromList $ filter filterEdges regularEdges
|
||||||
|
|
||||||
remapping :: M.Map Node Node
|
remapping :: M.Map Node Node
|
||||||
remapping = foldl' (\m (new, old) -> M.insert old new m) M.empty $ map orderEdge $ S.toList edgesToCollapse
|
remapping = M.fromList $ map orderEdge $ S.toList edgesToCollapse
|
||||||
recursiveRemapping = M.fromList $ map (\c -> (c, recursiveLookup remapping c)) $ M.keys remapping
|
recursiveRemapping = M.mapWithKey (\c _ -> recursiveLookup remapping c) remapping
|
||||||
|
|
||||||
filterEdges (a,b,_) =
|
filterEdges (a,b,_) =
|
||||||
a `S.member` candidateNodes && b `S.member` candidateNodes
|
a `S.member` candidateNodes && b `S.member` candidateNodes
|
||||||
|
|
||||||
orderEdge (a,b,_) = if a < b then (a,b) else (b,a)
|
orderEdge (a,b,_) = if a < b then (b,a) else (a,b)
|
||||||
counter = foldl' (\map key -> M.insertWith (+) key 1 map) M.empty
|
counter = M.fromListWith (+) . map (\key -> (key, 1))
|
||||||
isRegularEdge (_, _, CFEFlow) = True
|
isRegularEdge (_, _, CFEFlow) = True
|
||||||
isRegularEdge _ = False
|
isRegularEdge _ = False
|
||||||
|
|
||||||
|
@ -317,11 +317,6 @@ removeUnnecessaryStructuralNodes (nodes, edges, mapping, association) =
|
||||||
Nothing -> node
|
Nothing -> node
|
||||||
Just x -> recursiveLookup map x
|
Just x -> recursiveLookup map x
|
||||||
|
|
||||||
isStructural (node, label) =
|
|
||||||
case label of
|
|
||||||
CFStructuralNode -> True
|
|
||||||
_ -> False
|
|
||||||
|
|
||||||
isLinear node =
|
isLinear node =
|
||||||
M.findWithDefault 0 node inDegree == 1
|
M.findWithDefault 0 node inDegree == 1
|
||||||
&& M.findWithDefault 0 node outDegree == 1
|
&& M.findWithDefault 0 node outDegree == 1
|
||||||
|
@ -495,7 +490,7 @@ build t = do
|
||||||
TA_Binary _ _ a b -> sequentially [a,b]
|
TA_Binary _ _ a b -> sequentially [a,b]
|
||||||
TA_Expansion _ list -> sequentially list
|
TA_Expansion _ list -> sequentially list
|
||||||
TA_Sequence _ list -> sequentially list
|
TA_Sequence _ list -> sequentially list
|
||||||
TA_Parentesis _ t -> build t
|
TA_Parenthesis _ t -> build t
|
||||||
|
|
||||||
TA_Trinary _ cond a b -> do
|
TA_Trinary _ cond a b -> do
|
||||||
condition <- build cond
|
condition <- build cond
|
||||||
|
@ -673,10 +668,18 @@ build t = do
|
||||||
status <- newNodeRange $ CFSetExitCode id
|
status <- newNodeRange $ CFSetExitCode id
|
||||||
linkRange cond status
|
linkRange cond status
|
||||||
|
|
||||||
T_CoProc id maybeName t -> do
|
T_CoProc id maybeNameToken t -> do
|
||||||
let name = fromMaybe "COPROC" maybeName
|
-- If unspecified, "COPROC". If not a constant string, Nothing.
|
||||||
|
let maybeName = case maybeNameToken of
|
||||||
|
Just x -> getLiteralString x
|
||||||
|
Nothing -> Just "COPROC"
|
||||||
|
|
||||||
|
let parentNode = case maybeName of
|
||||||
|
Just str -> applySingle $ IdTagged id $ CFWriteVariable str CFValueArray
|
||||||
|
Nothing -> CFStructuralNode
|
||||||
|
|
||||||
start <- newStructuralNode
|
start <- newStructuralNode
|
||||||
parent <- newNodeRange $ applySingle $ IdTagged id $ CFWriteVariable name CFValueArray
|
parent <- newNodeRange parentNode
|
||||||
child <- subshell id "coproc" $ build t
|
child <- subshell id "coproc" $ build t
|
||||||
end <- newNodeRange $ CFSetExitCode id
|
end <- newNodeRange $ CFSetExitCode id
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ internalToExternal s =
|
||||||
literalValue = Nothing
|
literalValue = Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flatVars = M.unionsWith (\_ last -> last) $ map mapStorage [sGlobalValues s, sLocalValues s, sPrefixValues s]
|
flatVars = M.unions $ map mapStorage [sPrefixValues s, sLocalValues s, sGlobalValues s]
|
||||||
|
|
||||||
-- Conveniently get the state before a token id
|
-- Conveniently get the state before a token id
|
||||||
getIncomingState :: CFGAnalysis -> Id -> Maybe ProgramState
|
getIncomingState :: CFGAnalysis -> Id -> Maybe ProgramState
|
||||||
|
@ -672,7 +672,7 @@ vmPatch base diff =
|
||||||
_ | vmIsQuickEqual base diff -> diff
|
_ | vmIsQuickEqual base diff -> diff
|
||||||
_ -> VersionedMap {
|
_ -> VersionedMap {
|
||||||
mapVersion = -1,
|
mapVersion = -1,
|
||||||
mapStorage = M.unionWith (flip const) (mapStorage base) (mapStorage diff)
|
mapStorage = M.union (mapStorage diff) (mapStorage base)
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Set a variable. This includes properties. Applies it to the appropriate scope.
|
-- Set a variable. This includes properties. Applies it to the appropriate scope.
|
||||||
|
@ -1286,7 +1286,7 @@ dataflow ctx entry = do
|
||||||
else do
|
else do
|
||||||
let (next, rest) = S.deleteFindMin ps
|
let (next, rest) = S.deleteFindMin ps
|
||||||
nexts <- process states next
|
nexts <- process states next
|
||||||
writeSTRef pending $ foldl (flip S.insert) rest nexts
|
writeSTRef pending $ S.union (S.fromList nexts) rest
|
||||||
f (n-1) pending states
|
f (n-1) pending states
|
||||||
|
|
||||||
process states node = do
|
process states node = do
|
||||||
|
@ -1350,7 +1350,7 @@ analyzeControlFlow params t =
|
||||||
|
|
||||||
-- All nodes we've touched
|
-- All nodes we've touched
|
||||||
invocations <- readSTRef $ cInvocations ctx
|
invocations <- readSTRef $ cInvocations ctx
|
||||||
let invokedNodes = M.fromDistinctAscList $ map (\c -> (c, ())) $ S.toList $ M.keysSet $ groupByNode $ M.map snd invocations
|
let invokedNodes = M.fromSet (const ()) $ S.unions $ map (M.keysSet . snd) $ M.elems invocations
|
||||||
|
|
||||||
-- Invoke all functions that were declared but not invoked
|
-- Invoke all functions that were declared but not invoked
|
||||||
-- This is so that we still get warnings for dead code
|
-- This is so that we still get warnings for dead code
|
||||||
|
@ -1373,7 +1373,7 @@ analyzeControlFlow params t =
|
||||||
|
|
||||||
-- Fill in the map with unreachable states for anything we didn't get to
|
-- Fill in the map with unreachable states for anything we didn't get to
|
||||||
let baseStates = M.fromDistinctAscList $ map (\c -> (c, (unreachableState, unreachableState))) $ uncurry enumFromTo $ nodeRange $ cfGraph cfg
|
let baseStates = M.fromDistinctAscList $ map (\c -> (c, (unreachableState, unreachableState))) $ uncurry enumFromTo $ nodeRange $ cfGraph cfg
|
||||||
let allStates = M.unionWith (flip const) baseStates invokedStates
|
let allStates = M.union invokedStates baseStates
|
||||||
|
|
||||||
-- Convert to external states
|
-- Convert to external states
|
||||||
let nodeToData = M.map (\(a,b) -> (internalToExternal a, internalToExternal b)) allStates
|
let nodeToData = M.map (\(a,b) -> (internalToExternal a, internalToExternal b)) allStates
|
||||||
|
|
|
@ -1431,9 +1431,8 @@ prop_checkBackreferencingDeclaration7 = verify (checkBackreferencingDeclaration
|
||||||
checkBackreferencingDeclaration cmd = CommandCheck (Exactly cmd) check
|
checkBackreferencingDeclaration cmd = CommandCheck (Exactly cmd) check
|
||||||
where
|
where
|
||||||
check t = do
|
check t = do
|
||||||
cfga <- asks cfgAnalysis
|
maybeCfga <- asks cfgAnalysis
|
||||||
when (isJust cfga) $
|
mapM_ (\cfga -> foldM_ (perArg cfga) M.empty $ arguments t) maybeCfga
|
||||||
foldM_ (perArg $ fromJust cfga) M.empty $ arguments t
|
|
||||||
|
|
||||||
perArg cfga leftArgs t =
|
perArg cfga leftArgs t =
|
||||||
case t of
|
case t of
|
||||||
|
|
|
@ -78,7 +78,7 @@ controlFlowEffectChecks = [
|
||||||
runNodeChecks :: [ControlFlowNodeCheck] -> ControlFlowCheck
|
runNodeChecks :: [ControlFlowNodeCheck] -> ControlFlowCheck
|
||||||
runNodeChecks perNode = do
|
runNodeChecks perNode = do
|
||||||
cfg <- asks cfgAnalysis
|
cfg <- asks cfgAnalysis
|
||||||
sequence_ $ runOnAll <$> cfg
|
mapM_ runOnAll cfg
|
||||||
where
|
where
|
||||||
getData datas n@(node, label) = do
|
getData datas n@(node, label) = do
|
||||||
(pre, post) <- M.lookup node datas
|
(pre, post) <- M.lookup node datas
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{-
|
{-
|
||||||
This empty file is provided for ease of patching in site specific checks.
|
This empty file is provided for ease of patching in site specific checks.
|
||||||
However, there are no guarantees regarding compatibility between versions.
|
However, there are no guarantees regarding compatibility between versions.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
module ShellCheck.Checks.Custom (checker, ShellCheck.Checks.Custom.runTests) where
|
module ShellCheck.Checks.Custom (checker, ShellCheck.Checks.Custom.runTests) where
|
||||||
|
|
|
@ -63,6 +63,7 @@ checks = [
|
||||||
,checkPS1Assignments
|
,checkPS1Assignments
|
||||||
,checkMultipleBangs
|
,checkMultipleBangs
|
||||||
,checkBangAfterPipe
|
,checkBangAfterPipe
|
||||||
|
,checkNegatedUnaryOps
|
||||||
]
|
]
|
||||||
|
|
||||||
testChecker (ForShell _ t) =
|
testChecker (ForShell _ t) =
|
||||||
|
@ -86,7 +87,7 @@ checkForDecimals = ForShell [Sh, Dash, BusyboxSh, Bash] f
|
||||||
|
|
||||||
|
|
||||||
prop_checkBashisms = verify checkBashisms "while read a; do :; done < <(a)"
|
prop_checkBashisms = verify checkBashisms "while read a; do :; done < <(a)"
|
||||||
prop_checkBashisms2 = verify checkBashisms "[ foo -nt bar ]"
|
prop_checkBashisms2 = verifyNot checkBashisms "[ foo -nt bar ]"
|
||||||
prop_checkBashisms3 = verify checkBashisms "echo $((i++))"
|
prop_checkBashisms3 = verify checkBashisms "echo $((i++))"
|
||||||
prop_checkBashisms4 = verify checkBashisms "rm !(*.hs)"
|
prop_checkBashisms4 = verify checkBashisms "rm !(*.hs)"
|
||||||
prop_checkBashisms5 = verify checkBashisms "source file"
|
prop_checkBashisms5 = verify checkBashisms "source file"
|
||||||
|
@ -212,6 +213,16 @@ prop_checkBashisms118 = verify checkBashisms "#!/bin/busybox sh\nxyz=1\n${!x*}"
|
||||||
prop_checkBashisms119 = verify checkBashisms "#!/bin/busybox sh\nx='test'\n${x^^[t]}" -- SC3059
|
prop_checkBashisms119 = verify checkBashisms "#!/bin/busybox sh\nx='test'\n${x^^[t]}" -- SC3059
|
||||||
prop_checkBashisms120 = verify checkBashisms "#!/bin/sh\n[ x == y ]"
|
prop_checkBashisms120 = verify checkBashisms "#!/bin/sh\n[ x == y ]"
|
||||||
prop_checkBashisms121 = verifyNot checkBashisms "#!/bin/sh\n# shellcheck shell=busybox\n[ x == y ]"
|
prop_checkBashisms121 = verifyNot checkBashisms "#!/bin/sh\n# shellcheck shell=busybox\n[ x == y ]"
|
||||||
|
prop_checkBashisms122 = verify checkBashisms "#!/bin/dash\n$'a'"
|
||||||
|
prop_checkBashisms123 = verifyNot checkBashisms "#!/bin/busybox sh\n$'a'"
|
||||||
|
prop_checkBashisms124 = verify checkBashisms "#!/bin/dash\ntype -p test"
|
||||||
|
prop_checkBashisms125 = verifyNot checkBashisms "#!/bin/busybox sh\ntype -p test"
|
||||||
|
prop_checkBashisms126 = verifyNot checkBashisms "#!/bin/busybox sh\nread -p foo -r bar"
|
||||||
|
prop_checkBashisms127 = verifyNot checkBashisms "#!/bin/busybox sh\necho -ne foo"
|
||||||
|
prop_checkBashisms128 = verify checkBashisms "#!/bin/dash\ntype -p test"
|
||||||
|
prop_checkBashisms129 = verify checkBashisms "#!/bin/sh\n[ -k /tmp ]"
|
||||||
|
prop_checkBashisms130 = verifyNot checkBashisms "#!/bin/dash\ntest -k /tmp"
|
||||||
|
prop_checkBashisms131 = verify checkBashisms "#!/bin/sh\n[ -o errexit ]"
|
||||||
checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
params <- ask
|
params <- ask
|
||||||
kludge params t
|
kludge params t
|
||||||
|
@ -229,7 +240,8 @@ checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
|
|
||||||
bashism (T_ProcSub id _ _) = warnMsg id 3001 "process substitution is"
|
bashism (T_ProcSub id _ _) = warnMsg id 3001 "process substitution is"
|
||||||
bashism (T_Extglob id _ _) = warnMsg id 3002 "extglob is"
|
bashism (T_Extglob id _ _) = warnMsg id 3002 "extglob is"
|
||||||
bashism (T_DollarSingleQuoted id _) = warnMsg id 3003 "$'..' is"
|
bashism (T_DollarSingleQuoted id _) =
|
||||||
|
unless isBusyboxSh $ warnMsg id 3003 "$'..' is"
|
||||||
bashism (T_DollarDoubleQuoted id _) = warnMsg id 3004 "$\"..\" is"
|
bashism (T_DollarDoubleQuoted id _) = warnMsg id 3004 "$\"..\" is"
|
||||||
bashism (T_ForArithmetic id _ _ _ _) = warnMsg id 3005 "arithmetic for loops are"
|
bashism (T_ForArithmetic id _ _ _ _) = warnMsg id 3005 "arithmetic for loops are"
|
||||||
bashism (T_Arithmetic id _) = warnMsg id 3006 "standalone ((..)) is"
|
bashism (T_Arithmetic id _) = warnMsg id 3006 "standalone ((..)) is"
|
||||||
|
@ -239,34 +251,16 @@ checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
bashism (T_Condition id DoubleBracket _) =
|
bashism (T_Condition id DoubleBracket _) =
|
||||||
unless isBusyboxSh $ warnMsg id 3010 "[[ ]] is"
|
unless isBusyboxSh $ warnMsg id 3010 "[[ ]] is"
|
||||||
bashism (T_HereString id _) = warnMsg id 3011 "here-strings are"
|
bashism (T_HereString id _) = warnMsg id 3011 "here-strings are"
|
||||||
bashism (TC_Binary id SingleBracket op _ _)
|
|
||||||
| op `elem` [ "<", ">", "\\<", "\\>", "<=", ">=", "\\<=", "\\>="] =
|
bashism (TC_Binary id _ op _ _) =
|
||||||
unless isDash $ warnMsg id 3012 $ "lexicographical " ++ op ++ " is"
|
checkTestOp bashismBinaryTestFlags op id
|
||||||
bashism (T_SimpleCommand id _ [asStr -> Just "test", lhs, asStr -> Just op, rhs])
|
bashism (T_SimpleCommand id _ [asStr -> Just "test", lhs, asStr -> Just op, rhs]) =
|
||||||
| op `elem` [ "<", ">", "\\<", "\\>", "<=", ">=", "\\<=", "\\>="] =
|
checkTestOp bashismBinaryTestFlags op id
|
||||||
unless isDash $ warnMsg id 3012 $ "lexicographical " ++ op ++ " is"
|
bashism (TC_Unary id _ op _) =
|
||||||
bashism (TC_Binary id SingleBracket op _ _)
|
checkTestOp bashismUnaryTestFlags op id
|
||||||
| op `elem` [ "-ot", "-nt", "-ef" ] =
|
bashism (T_SimpleCommand id _ [asStr -> Just "test", asStr -> Just op, _]) =
|
||||||
unless isDash $ warnMsg id 3013 $ op ++ " is"
|
checkTestOp bashismUnaryTestFlags op id
|
||||||
bashism (T_SimpleCommand id _ [asStr -> Just "test", lhs, asStr -> Just op, rhs])
|
|
||||||
| op `elem` [ "-ot", "-nt", "-ef" ] =
|
|
||||||
unless isDash $ warnMsg id 3013 $ op ++ " is"
|
|
||||||
bashism (TC_Binary id SingleBracket "==" _ _) =
|
|
||||||
unless isBusyboxSh $ warnMsg id 3014 "== in place of = is"
|
|
||||||
bashism (T_SimpleCommand id _ [asStr -> Just "test", lhs, asStr -> Just "==", rhs]) =
|
|
||||||
unless isBusyboxSh $ warnMsg id 3014 "== in place of = is"
|
|
||||||
bashism (TC_Binary id SingleBracket "=~" _ _) =
|
|
||||||
warnMsg id 3015 "=~ regex matching is"
|
|
||||||
bashism (T_SimpleCommand id _ [asStr -> Just "test", lhs, asStr -> Just "=~", rhs]) =
|
|
||||||
warnMsg id 3015 "=~ regex matching is"
|
|
||||||
bashism (TC_Unary id SingleBracket "-v" _) =
|
|
||||||
warnMsg id 3016 "unary -v (in place of [ -n \"${var+x}\" ]) is"
|
|
||||||
bashism (T_SimpleCommand id _ [asStr -> Just "test", asStr -> Just "-v", _]) =
|
|
||||||
warnMsg id 3016 "unary -v (in place of [ -n \"${var+x}\" ]) is"
|
|
||||||
bashism (TC_Unary id _ "-a" _) =
|
|
||||||
warnMsg id 3017 "unary -a in place of -e is"
|
|
||||||
bashism (T_SimpleCommand id _ [asStr -> Just "test", asStr -> Just "-a", _]) =
|
|
||||||
warnMsg id 3017 "unary -a in place of -e is"
|
|
||||||
bashism (TA_Unary id op _)
|
bashism (TA_Unary id op _)
|
||||||
| op `elem` [ "|++", "|--", "++|", "--|"] =
|
| op `elem` [ "|++", "|--", "++|", "--|"] =
|
||||||
warnMsg id 3018 $ filter (/= '|') op ++ " is"
|
warnMsg id 3018 $ filter (/= '|') op ++ " is"
|
||||||
|
@ -321,7 +315,11 @@ checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
|
|
||||||
bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
|
bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
|
||||||
| t `isCommand` "echo" && argString `matches` flagRegex =
|
| t `isCommand` "echo" && argString `matches` flagRegex =
|
||||||
if isDash
|
if isBusyboxSh
|
||||||
|
then
|
||||||
|
unless (argString `matches` busyboxFlagRegex) $
|
||||||
|
warnMsg (getId arg) 3036 "echo flags besides -n and -e"
|
||||||
|
else if isDash
|
||||||
then
|
then
|
||||||
when (argString /= "-n") $
|
when (argString /= "-n") $
|
||||||
warnMsg (getId arg) 3036 "echo flags besides -n"
|
warnMsg (getId arg) 3036 "echo flags besides -n"
|
||||||
|
@ -330,6 +328,7 @@ checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
where
|
where
|
||||||
argString = concat $ oversimplify arg
|
argString = concat $ oversimplify arg
|
||||||
flagRegex = mkRegex "^-[eEsn]+$"
|
flagRegex = mkRegex "^-[eEsn]+$"
|
||||||
|
busyboxFlagRegex = mkRegex "^-[en]+$"
|
||||||
|
|
||||||
bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
|
bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
|
||||||
| getLiteralString cmd == Just "exec" && "-" `isPrefixOf` concat (oversimplify arg) =
|
| getLiteralString cmd == Just "exec" && "-" `isPrefixOf` concat (oversimplify arg) =
|
||||||
|
@ -443,10 +442,10 @@ checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
("hash", Just $ if isDash then ["r", "v"] else ["r"]),
|
("hash", Just $ if isDash then ["r", "v"] else ["r"]),
|
||||||
("jobs", Just ["l", "p"]),
|
("jobs", Just ["l", "p"]),
|
||||||
("printf", Just []),
|
("printf", Just []),
|
||||||
("read", Just $ if isDash then ["r", "p"] else ["r"]),
|
("read", Just $ if isDash || isBusyboxSh then ["r", "p"] else ["r"]),
|
||||||
("readonly", Just ["p"]),
|
("readonly", Just ["p"]),
|
||||||
("trap", Just []),
|
("trap", Just []),
|
||||||
("type", Just []),
|
("type", Just $ if isBusyboxSh then ["p"] else []),
|
||||||
("ulimit", if isDash then Nothing else Just ["f"]),
|
("ulimit", if isDash then Nothing else Just ["f"]),
|
||||||
("umask", Just ["S"]),
|
("umask", Just ["S"]),
|
||||||
("unset", Just ["f", "v"]),
|
("unset", Just ["f", "v"]),
|
||||||
|
@ -498,6 +497,50 @@ checkBashisms = ForShell [Sh, Dash, BusyboxSh] $ \t -> do
|
||||||
Assignment (_, _, name, _) -> name == var
|
Assignment (_, _, name, _) -> name == var
|
||||||
_ -> False
|
_ -> False
|
||||||
|
|
||||||
|
checkTestOp table op id = sequence_ $ do
|
||||||
|
(code, shells, msg) <- Map.lookup op table
|
||||||
|
guard . not $ shellType params `elem` shells
|
||||||
|
return $ warnMsg id code (msg op)
|
||||||
|
|
||||||
|
|
||||||
|
buildTestFlagMap list = Map.fromList $ concatMap (\(x,y) -> map (\c -> (c,y)) x) list
|
||||||
|
bashismBinaryTestFlags = buildTestFlagMap [
|
||||||
|
-- ([list of applicable flags],
|
||||||
|
-- (error code, exempt shells, message builder :: String -> String)),
|
||||||
|
--
|
||||||
|
-- Distinct error codes allow the wiki to give more helpful, targeted
|
||||||
|
-- information.
|
||||||
|
(["<", ">", "\\<", "\\>", "<=", ">=", "\\<=", "\\>="],
|
||||||
|
(3012, [Dash, BusyboxSh], \op -> "lexicographical " ++ op ++ " is")),
|
||||||
|
(["=="],
|
||||||
|
(3014, [BusyboxSh], \op -> op ++ " in place of = is")),
|
||||||
|
(["=~"],
|
||||||
|
(3015, [], \op -> op ++ " regex matching is")),
|
||||||
|
|
||||||
|
([], (0,[],const ""))
|
||||||
|
]
|
||||||
|
bashismUnaryTestFlags = buildTestFlagMap [
|
||||||
|
(["-v"],
|
||||||
|
(3016, [], \op -> "test " ++ op ++ " (in place of [ -n \"${var+x}\" ]) is")),
|
||||||
|
(["-a"],
|
||||||
|
(3017, [], \op -> "unary " ++ op ++ " in place of -e is")),
|
||||||
|
(["-o"],
|
||||||
|
(3062, [], \op -> "test " ++ op ++ " to check options is")),
|
||||||
|
(["-R"],
|
||||||
|
(3063, [], \op -> "test " ++ op ++ " and namerefs in general are")),
|
||||||
|
(["-N"],
|
||||||
|
(3064, [], \op -> "test " ++ op ++ " is")),
|
||||||
|
(["-k"],
|
||||||
|
(3065, [Dash, BusyboxSh], \op -> "test " ++ op ++ " is")),
|
||||||
|
(["-G"],
|
||||||
|
(3066, [Dash, BusyboxSh], \op -> "test " ++ op ++ " is")),
|
||||||
|
(["-O"],
|
||||||
|
(3067, [Dash, BusyboxSh], \op -> "test " ++ op ++ " is")),
|
||||||
|
|
||||||
|
([], (0,[],const ""))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
prop_checkEchoSed1 = verify checkEchoSed "FOO=$(echo \"$cow\" | sed 's/foo/bar/g')"
|
prop_checkEchoSed1 = verify checkEchoSed "FOO=$(echo \"$cow\" | sed 's/foo/bar/g')"
|
||||||
prop_checkEchoSed1b = verify checkEchoSed "FOO=$(sed 's/foo/bar/g' <<< \"$cow\")"
|
prop_checkEchoSed1b = verify checkEchoSed "FOO=$(sed 's/foo/bar/g' <<< \"$cow\")"
|
||||||
prop_checkEchoSed2 = verify checkEchoSed "rm $(echo $cow | sed -e 's,foo,bar,')"
|
prop_checkEchoSed2 = verify checkEchoSed "rm $(echo $cow | sed -e 's,foo,bar,')"
|
||||||
|
@ -637,5 +680,22 @@ checkBangAfterPipe = ForShell [Dash, BusyboxSh, Sh, Bash] f
|
||||||
err id 2326 "! is not allowed in the middle of pipelines. Use command group as in cmd | { ! cmd; } if necessary."
|
err id 2326 "! is not allowed in the middle of pipelines. Use command group as in cmd | { ! cmd; } if necessary."
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
|
|
||||||
|
|
||||||
|
prop_checkNegatedUnaryOps1 = verify checkNegatedUnaryOps "[ ! -o braceexpand ]"
|
||||||
|
prop_checkNegatedUnaryOps2 = verifyNot checkNegatedUnaryOps "[ -o braceexpand ]"
|
||||||
|
prop_checkNegatedUnaryOps3 = verifyNot checkNegatedUnaryOps "[[ ! -o braceexpand ]]"
|
||||||
|
prop_checkNegatedUnaryOps4 = verifyNot checkNegatedUnaryOps "! [ -o braceexpand ]"
|
||||||
|
prop_checkNegatedUnaryOps5 = verify checkNegatedUnaryOps "[ ! -a file ]"
|
||||||
|
checkNegatedUnaryOps = ForShell [Bash] f
|
||||||
|
where
|
||||||
|
f token = case token of
|
||||||
|
TC_Unary id SingleBracket "!" (TC_Unary _ _ op _) | op `elem` ["-a", "-o"] ->
|
||||||
|
err id 2332 $ msg op
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
|
msg "-o" = "[ ! -o opt ] is always true because -o becomes logical OR. Use [[ ]] or ! [ -o opt ]."
|
||||||
|
msg "-a" = "[ ! -a file ] is always true because -a becomes logical AND. Use -e instead."
|
||||||
|
msg _ = pleaseReport "unhandled negated unary message"
|
||||||
|
|
||||||
return []
|
return []
|
||||||
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
||||||
|
|
|
@ -49,6 +49,7 @@ internalVariables = [
|
||||||
"LINES", "MAIL", "MAILCHECK", "MAILPATH", "OPTERR", "PATH",
|
"LINES", "MAIL", "MAILCHECK", "MAILPATH", "OPTERR", "PATH",
|
||||||
"POSIXLY_CORRECT", "PROMPT_COMMAND", "PROMPT_DIRTRIM", "PS0", "PS1",
|
"POSIXLY_CORRECT", "PROMPT_COMMAND", "PROMPT_DIRTRIM", "PS0", "PS1",
|
||||||
"PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT", "TMOUT", "TMPDIR",
|
"PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT", "TMOUT", "TMPDIR",
|
||||||
|
"BASH_MONOSECONDS", "BASH_TRAPSIG", "GLOBSORT",
|
||||||
"auto_resume", "histchars",
|
"auto_resume", "histchars",
|
||||||
|
|
||||||
-- Other
|
-- Other
|
||||||
|
@ -62,6 +63,9 @@ internalVariables = [
|
||||||
, "FLAGS_ARGC", "FLAGS_ARGV", "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_HELP",
|
, "FLAGS_ARGC", "FLAGS_ARGV", "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_HELP",
|
||||||
"FLAGS_PARENT", "FLAGS_RESERVED", "FLAGS_TRUE", "FLAGS_VERSION",
|
"FLAGS_PARENT", "FLAGS_RESERVED", "FLAGS_TRUE", "FLAGS_VERSION",
|
||||||
"flags_error", "flags_return"
|
"flags_error", "flags_return"
|
||||||
|
|
||||||
|
-- Bats
|
||||||
|
,"stderr", "stderr_lines"
|
||||||
]
|
]
|
||||||
|
|
||||||
specialIntegerVariables = [
|
specialIntegerVariables = [
|
||||||
|
@ -75,7 +79,7 @@ variablesWithoutSpaces = specialVariablesWithoutSpaces ++ [
|
||||||
"EPOCHREALTIME", "EPOCHSECONDS", "LINENO", "OPTIND", "PPID", "RANDOM",
|
"EPOCHREALTIME", "EPOCHSECONDS", "LINENO", "OPTIND", "PPID", "RANDOM",
|
||||||
"READLINE_ARGUMENT", "READLINE_MARK", "READLINE_POINT", "SECONDS",
|
"READLINE_ARGUMENT", "READLINE_MARK", "READLINE_POINT", "SECONDS",
|
||||||
"SHELLOPTS", "SHLVL", "SRANDOM", "UID", "COLUMNS", "HISTFILESIZE",
|
"SHELLOPTS", "SHLVL", "SRANDOM", "UID", "COLUMNS", "HISTFILESIZE",
|
||||||
"HISTSIZE", "LINES"
|
"HISTSIZE", "LINES", "BASH_MONOSECONDS", "BASH_TRAPSIG"
|
||||||
|
|
||||||
-- shflags
|
-- shflags
|
||||||
, "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_TRUE"
|
, "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_TRUE"
|
||||||
|
@ -164,6 +168,7 @@ shellForExecutable name =
|
||||||
"ksh" -> return Ksh
|
"ksh" -> return Ksh
|
||||||
"ksh88" -> return Ksh
|
"ksh88" -> return Ksh
|
||||||
"ksh93" -> return Ksh
|
"ksh93" -> return Ksh
|
||||||
|
"oksh" -> return Ksh
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
flagsForRead = "sreu:n:N:i:p:a:t:"
|
flagsForRead = "sreu:n:N:i:p:a:t:"
|
||||||
|
|
|
@ -169,7 +169,7 @@ showFixedString color comments lineNum fileLines =
|
||||||
-- and/or other unrelated lines.
|
-- and/or other unrelated lines.
|
||||||
let (excerptFix, excerpt) = sliceFile mergedFix fileLines
|
let (excerptFix, excerpt) = sliceFile mergedFix fileLines
|
||||||
-- in the spirit of error prone
|
-- in the spirit of error prone
|
||||||
putStrLn $ color "message" "Did you mean: "
|
putStrLn $ color "message" "Did you mean:"
|
||||||
putStrLn $ unlines $ applyFix excerptFix excerpt
|
putStrLn $ unlines $ applyFix excerptFix excerpt
|
||||||
|
|
||||||
cuteIndent :: PositionedComment -> String
|
cuteIndent :: PositionedComment -> String
|
||||||
|
|
|
@ -141,15 +141,9 @@ carriageReturn = do
|
||||||
parseProblemAt pos ErrorC 1017 "Literal carriage return. Run script through tr -d '\\r' ."
|
parseProblemAt pos ErrorC 1017 "Literal carriage return. Run script through tr -d '\\r' ."
|
||||||
return '\r'
|
return '\r'
|
||||||
|
|
||||||
almostSpace =
|
almostSpace = do
|
||||||
choice [
|
parseNote ErrorC 1018 $ "This is a unicode space. Delete and retype it."
|
||||||
check '\xA0' "unicode non-breaking space",
|
oneOf "\xA0\x2002\x2003\x2004\x2005\x2006\x2007\x2008\x2009\x200B\x202F"
|
||||||
check '\x200B' "unicode zerowidth space"
|
|
||||||
]
|
|
||||||
where
|
|
||||||
check c name = do
|
|
||||||
parseNote ErrorC 1018 $ "This is a " ++ name ++ ". Delete and retype it."
|
|
||||||
char c
|
|
||||||
return ' '
|
return ' '
|
||||||
|
|
||||||
--------- Message/position annotation on top of user state
|
--------- Message/position annotation on top of user state
|
||||||
|
@ -827,7 +821,7 @@ readArithmeticContents =
|
||||||
char ')'
|
char ')'
|
||||||
id <- endSpan start
|
id <- endSpan start
|
||||||
spacing
|
spacing
|
||||||
return $ TA_Parentesis id s
|
return $ TA_Parenthesis id s
|
||||||
|
|
||||||
readArithTerm = readGroup <|> readVariable <|> readExpansion
|
readArithTerm = readGroup <|> readVariable <|> readExpansion
|
||||||
|
|
||||||
|
@ -2801,17 +2795,29 @@ readFunctionDefinition = called "function" $ do
|
||||||
prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }"
|
prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }"
|
||||||
prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }"
|
prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }"
|
||||||
prop_readCoProc3 = isOk readCoProc "coproc echo bar"
|
prop_readCoProc3 = isOk readCoProc "coproc echo bar"
|
||||||
|
prop_readCoProc4 = isOk readCoProc "coproc a=b echo bar"
|
||||||
|
prop_readCoProc5 = isOk readCoProc "coproc 'foo' { echo bar; }"
|
||||||
|
prop_readCoProc6 = isOk readCoProc "coproc \"foo$$\" { echo bar; }"
|
||||||
|
prop_readCoProc7 = isOk readCoProc "coproc 'foo' ( echo bar )"
|
||||||
|
prop_readCoProc8 = isOk readCoProc "coproc \"foo$$\" while true; do true; done"
|
||||||
readCoProc = called "coproc" $ do
|
readCoProc = called "coproc" $ do
|
||||||
start <- startSpan
|
start <- startSpan
|
||||||
try $ do
|
try $ do
|
||||||
string "coproc"
|
string "coproc"
|
||||||
whitespace
|
spacing1
|
||||||
choice [ try $ readCompoundCoProc start, readSimpleCoProc start ]
|
choice [ try $ readCompoundCoProc start, readSimpleCoProc start ]
|
||||||
where
|
where
|
||||||
readCompoundCoProc start = do
|
readCompoundCoProc start = do
|
||||||
var <- optionMaybe $
|
notFollowedBy2 readAssignmentWord
|
||||||
readVariableName `thenSkip` whitespace
|
(var, body) <- choice [
|
||||||
body <- readBody readCompoundCommand
|
try $ do
|
||||||
|
body <- readBody readCompoundCommand
|
||||||
|
return (Nothing, body),
|
||||||
|
try $ do
|
||||||
|
var <- readNormalWord `thenSkip` spacing
|
||||||
|
body <- readBody readCompoundCommand
|
||||||
|
return (Just var, body)
|
||||||
|
]
|
||||||
id <- endSpan start
|
id <- endSpan start
|
||||||
return $ T_CoProc id var body
|
return $ T_CoProc id var body
|
||||||
readSimpleCoProc start = do
|
readSimpleCoProc start = do
|
||||||
|
@ -3381,7 +3387,8 @@ readScriptFile sourced = do
|
||||||
"busybox sh",
|
"busybox sh",
|
||||||
"bash",
|
"bash",
|
||||||
"bats",
|
"bats",
|
||||||
"ksh"
|
"ksh",
|
||||||
|
"oksh"
|
||||||
]
|
]
|
||||||
badShells = [
|
badShells = [
|
||||||
"awk",
|
"awk",
|
||||||
|
@ -3390,6 +3397,7 @@ readScriptFile sourced = do
|
||||||
"fish",
|
"fish",
|
||||||
"perl",
|
"perl",
|
||||||
"python",
|
"python",
|
||||||
|
"python3",
|
||||||
"ruby",
|
"ruby",
|
||||||
"tcsh",
|
"tcsh",
|
||||||
"zsh"
|
"zsh"
|
||||||
|
@ -3442,13 +3450,22 @@ isOk p s = parsesCleanly p s == Just True -- The string parses with no wa
|
||||||
isWarning p s = parsesCleanly p s == Just False -- The string parses with warnings
|
isWarning p s = parsesCleanly p s == Just False -- The string parses with warnings
|
||||||
isNotOk p s = parsesCleanly p s == Nothing -- The string does not parse
|
isNotOk p s = parsesCleanly p s == Nothing -- The string does not parse
|
||||||
|
|
||||||
parsesCleanly parser string = runIdentity $ do
|
-- If the parser matches the string, return Right [ParseNotes+ParseProblems]
|
||||||
(res, sys) <- runParser testEnvironment
|
-- If it does not match the string, return Left [ParseProblems]
|
||||||
(parser >> eof >> getState) "-" string
|
getParseOutput parser string = runIdentity $ do
|
||||||
case (res, sys) of
|
(res, systemState) <- runParser testEnvironment
|
||||||
(Right userState, systemState) ->
|
(parser >> eof >> getState) "-" string
|
||||||
return $ Just . null $ parseNotes userState ++ parseProblems systemState
|
return $ case res of
|
||||||
(Left _, _) -> return Nothing
|
Right userState ->
|
||||||
|
Right $ parseNotes userState ++ parseProblems systemState
|
||||||
|
Left _ -> Left $ parseProblems systemState
|
||||||
|
|
||||||
|
-- If the parser matches the string, return Just whether it was clean (without emitting suggestions)
|
||||||
|
-- Otherwise, Nothing
|
||||||
|
parsesCleanly parser string =
|
||||||
|
case getParseOutput parser string of
|
||||||
|
Right list -> Just $ null list
|
||||||
|
Left _ -> Nothing
|
||||||
|
|
||||||
parseWithNotes parser = do
|
parseWithNotes parser = do
|
||||||
item <- parser
|
item <- parser
|
||||||
|
|
|
@ -12,6 +12,17 @@ then
|
||||||
fail "There are uncommitted changes"
|
fail "There are uncommitted changes"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
version=${current#v}
|
||||||
|
if ! grep "Version:" ShellCheck.cabal | grep -qFw "$version"
|
||||||
|
then
|
||||||
|
fail "The cabal file does not match tag version $version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -qF "## $current" CHANGELOG.md
|
||||||
|
then
|
||||||
|
fail "CHANGELOG.md does not contain '## $current'"
|
||||||
|
fi
|
||||||
|
|
||||||
current=$(git tag --points-at)
|
current=$(git tag --points-at)
|
||||||
if [[ -z "$current" ]]
|
if [[ -z "$current" ]]
|
||||||
then
|
then
|
||||||
|
@ -34,33 +45,30 @@ then
|
||||||
fail "You are not on master"
|
fail "You are not on master"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
version=${current#v}
|
|
||||||
if ! grep "Version:" ShellCheck.cabal | grep -qFw "$version"
|
|
||||||
then
|
|
||||||
fail "The cabal file does not match tag version $version"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -qF "## $current" CHANGELOG.md
|
|
||||||
then
|
|
||||||
fail "CHANGELOG.md does not contain '## $current'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $(git log -1 --pretty=%B) != "Stable version "* ]]
|
if [[ $(git log -1 --pretty=%B) != "Stable version "* ]]
|
||||||
then
|
then
|
||||||
fail "Expected git log message to be 'Stable version ...'"
|
fail "Expected git log message to be 'Stable version ...'"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ $(git log -1 --pretty=%B) != *"CHANGELOG"* ]]
|
||||||
|
then
|
||||||
|
fail "Expected git log message to contain CHANGELOG"
|
||||||
|
fi
|
||||||
|
|
||||||
i=1 j=1
|
i=1 j=1
|
||||||
cat << EOF
|
cat << EOF
|
||||||
|
|
||||||
Manual Checklist
|
Manual Checklist
|
||||||
|
|
||||||
$((i++)). Make sure none of the automated checks above failed
|
$((i++)). Make sure none of the automated checks above failed
|
||||||
|
$((i++)). Run \`build/build_builder build/*/\` to update all builder images.
|
||||||
|
$((j++)). \`build/run_builder dist-newstyle/sdist/ShellCheck-*.tar.gz build/*/\` to verify that they work.
|
||||||
|
$((j++)). \`for f in \$(cat build/*/tag); do docker push "\$f"; done\` to upload them.
|
||||||
|
$((i++)). Run test/distrotest to ensure that most distros can build OOTB.
|
||||||
$((i++)). Make sure GitHub Build currently passes: https://github.com/koalaman/shellcheck/actions
|
$((i++)). Make sure GitHub Build currently passes: https://github.com/koalaman/shellcheck/actions
|
||||||
$((i++)). Make sure SnapCraft build currently works: https://build.snapcraft.io/user/koalaman
|
$((i++)). Make sure SnapCraft build currently works: https://build.snapcraft.io/user/koalaman
|
||||||
$((i++)). Run test/distrotest to ensure that most distros can build OOTB.
|
|
||||||
$((i++)). Format and read over the manual for bad formatting and outdated info.
|
$((i++)). Format and read over the manual for bad formatting and outdated info.
|
||||||
$((i++)). Make sure the Hackage package builds.
|
$((i++)). Make sure the Hackage package builds locally.
|
||||||
|
|
||||||
Release Steps
|
Release Steps
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,13 @@ 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.
|
Also note that dist*/ and .stack-work/ will be deleted.
|
||||||
EOF
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Deleting 'dist' and 'dist-newstyle'..."
|
echo "Deleting 'dist', 'dist-newstyle', and '.stack-work'..."
|
||||||
rm -rf dist dist-newstyle
|
rm -rf dist dist-newstyle .stack-work
|
||||||
|
|
||||||
execs=$(find . -name shellcheck)
|
execs=$(find . -name shellcheck)
|
||||||
|
|
||||||
|
@ -74,11 +74,12 @@ fedora:latest dnf install -y cabal-install ghc-template-haskell-devel fi
|
||||||
archlinux:latest pacman -S -y --noconfirm cabal-install ghc-static base-devel
|
archlinux:latest pacman -S -y --noconfirm cabal-install ghc-static base-devel
|
||||||
|
|
||||||
# Ubuntu LTS
|
# Ubuntu LTS
|
||||||
|
ubuntu:24.04 apt-get update && apt-get install -y cabal-install
|
||||||
ubuntu:22.04 apt-get update && apt-get install -y cabal-install
|
ubuntu:22.04 apt-get update && apt-get install -y cabal-install
|
||||||
ubuntu:20.04 apt-get update && apt-get install -y cabal-install
|
ubuntu:20.04 apt-get update && apt-get install -y cabal-install
|
||||||
|
|
||||||
# Stack on Ubuntu LTS
|
# Stack on Ubuntu LTS
|
||||||
ubuntu:22.04 set -e; apt-get update && apt-get install -y curl && curl -sSL https://get.haskellstack.org/ | sh -s - -f && cd /mnt && exec test/stacktest
|
ubuntu:24.04 set -e; apt-get update && apt-get install -y curl && curl -sSL https://get.haskellstack.org/ | sh -s - -f && cd /mnt && exec test/stacktest
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
exit "$final"
|
exit "$final"
|
||||||
|
|
|
@ -15,7 +15,7 @@ die() { echo "$*" >&2; exit 1; }
|
||||||
command -v stack ||
|
command -v stack ||
|
||||||
die "stack is missing"
|
die "stack is missing"
|
||||||
|
|
||||||
stack setup || die "Failed to setup with default resolver"
|
stack setup --allow-different-user || die "Failed to setup with default resolver"
|
||||||
stack build --test || die "Failed to build/test with default resolver"
|
stack build --test || die "Failed to build/test with default resolver"
|
||||||
|
|
||||||
# Nice to haves, but not necessary
|
# Nice to haves, but not necessary
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue