ci/lint.sh
#!/usr/bin/env bash
# vim: set tabstop=2 shiftwidth=2 expandtab
set -u -e -o pipefail
[[ -n ${DEBUG:-} ]] && set -x
if [[ "$(uname -s)" != "Linux" ]]; then
echo "This script is only intended to be run on Linux as the used CLI tools might not be available or differ in their semantics."
exit 1
fi
readonly PROJECT_ROOT="$(dirname "${BASH_SOURCE[0]}")/.."
cd "${PROJECT_ROOT}"
# Ensure linter versions are specified or set the default values.
readonly GOLANGCI_VERSION="${GOLANGCI_VERSION:-"1.27.0"}"
readonly MARKDOWNLINT_VERSION="${MARKDOWNLINT_VERSION:-"0.9.0"}"
readonly SHELLCHECK_VERSION="${SHELLCHECK_VERSION:-"0.7.1"}"
readonly SHFMT_VERSION="${SHFMT_VERSION:-"3.1.1"}"
readonly YAMLLINT_VERSION="${YAMLLINT_VERSION:-"1.23.0"}"
# Retrieve linters if necessary.
mkdir -p "${PWD}/bin"
export PATH="${PWD}/bin:${PATH}"
## golangci-lint
if [[ -z "$(command -v golangci-lint)" ]] || ! grep "${GOLANGCI_VERSION}" <<<"$(golangci-lint --version)"; then
echo "Installing golangci-lint@${GOLANGCI_VERSION}."
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY="golang-ci" bash -s -- -b "${PWD}/bin" "v${GOLANGCI_VERSION}"
else
echo "Found installed golangci-lint@${GOLANGCI_VERSION}."
fi
## shellcheck
if [[ -z "$(command -v shellcheck)" ]] || ! grep "${SHELLCHECK_VERSION}" <<<"$(shellcheck --version)"; then
echo "Installing shellcheck@${SHELLCHECK_VERSION}."
curl -LSs "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" |
tar --extract --xz --strip-components=1 --directory="${PWD}/bin" "shellcheck-v${SHELLCHECK_VERSION}/shellcheck"
else
echo "Found installed shellcheck@${SHELLCHECK_VERSION}."
fi
# shfmt
if [[ -z "$(command -v shfmt)" ]] || ! grep "${SHFMT_VERSION}" <<<"$(shfmt -version)"; then
echo "Installing shfmt."
GOBIN="${PWD}/bin" go install -modfile=tools.mod "mvdan.cc/sh/v3/cmd/shfmt"
else
echo "Found installed shfmt."
fi
## markdownlint
if [[ -z "$(command -v mdl)" ]] || ! grep "${MARKDOWNLINT_VERSION}" <<<"$(mdl --version)"; then
echo "Installing mdl@${MARKDOWNLINT_VERSION}."
mkdir -p "${HOME}/.ruby"
export GEM_HOME="${HOME}/.ruby"
gem install mdl "--version=${MARKDOWNLINT_VERSION}" --bindir=./bin
else
echo "Found installed mdl@${MARKDOWNLINT_VERSION}."
fi
## yamllint
if [[ -z "$(command -v yamllint)" ]] || ! grep "${YAMLLINT_VERSION}" <<<"$(yamllint --version)"; then
echo "Installing yamllint@${YAMLLINT_VERSION}."
pip install "yamllint==${YAMLLINT_VERSION}"
else
echo "Found installed yamllint@${YAMLLINT_VERSION}."
fi
# Run linters.
echo "Ensuring that generated Go code is being kept up to date."
go generate ./...
git diff --exit-code --quiet '*.go' || (
echo "Please run 'go generate ./...' to update the generated Go code."
false
)
echo "Linting YAML files."
yamllint --strict --config-file=./.yamllint.yaml .
echo "Linting Go source code."
golangci-lint run ./...
echo "Ensuring that 'go.mod' and 'go.sum' are being kept up to date."
go mod tidy
git diff --exit-code --quiet go.mod go.sum || (
echo "Please run 'go mod tidy' to clean up the 'go.mod' and 'go.sum' files."
false
)
echo "Linting Bash scripts."
declare -a shell_files
while read -r file; do
shell_files+=("${file}")
done <<<"$(shfmt -f .)"
shellcheck --external-sources --shell=bash --severity=style "${shell_files[@]}"
shell_failure=0
readonly shell_vim_directives="# vim: set tabstop=2 shiftwidth=2 expandtab"
for shell_file in "${shell_files[@]}"; do
if ! grep -q "^${shell_vim_directives}$" "${shell_file}"; then
echo "'${shell_file}' is missing the compulsory VIm directives: ${shell_vim_directives}"
shell_failure=1
fi
done
if ((shell_failure == 1)); then
echo "Errors were detected while linting shell scripts."
exit 1
fi
echo "Checking the formatting of Bash scripts."
shfmt -i 2 -s -w -d .
echo "Linting Markdown files."
mdl .