From 5564c0200c91fed709de6dc3ee1eaa4298d097bf Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Mon, 3 Feb 2025 03:30:53 +0900 Subject: [PATCH] tools: Clean up tidy.sh --- tools/tidy.sh | 306 ++++++++++++++++++++++---------------------------- 1 file changed, 132 insertions(+), 174 deletions(-) diff --git a/tools/tidy.sh b/tools/tidy.sh index 44e1b3c..2b3388c 100755 --- a/tools/tidy.sh +++ b/tools/tidy.sh @@ -25,6 +25,39 @@ cd -- "$(dirname -- "$0")"/.. # checks for files not included in this repository, but they will be # skipped if the corresponding files do not exist. +retry() { + for i in {1..10}; do + if "$@"; then + return 0 + else + sleep "${i}" + fi + done + "$@" +} +error() { + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + printf '::error::%s\n' "$*" + else + printf >&2 'error: %s\n' "$*" + fi + should_fail=1 +} +warn() { + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + printf '::warning::%s\n' "$*" + else + printf >&2 'warning: %s\n' "$*" + fi +} +info() { + printf >&2 'info: %s\n' "$*" +} +print_fenced() { + printf '=======================================\n' + printf '%s' "$*" + printf '=======================================\n\n' +} check_diff() { if [[ -n "${CI:-}" ]]; then if ! git --no-pager diff --exit-code "$@"; then @@ -41,28 +74,10 @@ check_config() { error "could not found $1 in the repository root${2:-}" fi } -check_hidden() { - if [[ -n "$(comm -23 <(ls_files "*$1") <(ls_files "*.$1"))" ]]; then - error "please use '.$1' instead of '$1' for consistency" - printf '=======================================\n' - comm -23 <(ls_files "*$1") <(ls_files "*.$1") - printf '=======================================\n\n' - fi -} -check_alt() { - local recommended="$1" - shift - if [[ -n "$(ls_files "$@")" ]]; then - error "please use '${recommended}' instead of the following for consistency" - printf '=======================================\n' - ls_files "$@" - printf '=======================================\n\n' - fi -} check_install() { for tool in "$@"; do if ! type -P "${tool}" >/dev/null; then - if [[ "${tool}" == "python3" ]]; then + if [[ "${tool}" == 'python3' ]]; then if type -P python >/dev/null; then continue fi @@ -72,33 +87,29 @@ check_install() { fi done } -retry() { - for i in {1..10}; do - if "$@"; then - return 0 - else - sleep "${i}" - fi - done - "$@" -} -error() { - if [[ -n "${GITHUB_ACTIONS:-}" ]]; then - printf '::error::%s\n' "$*" - else - printf >&2 'error: %s\n' "$*" +check_unused() { + local kind="$1" + shift + local res + res=$(ls_files "$@") + if [[ -n "${res}" ]]; then + error "the following files are unused because there is no ${kind}; consider removing them" + print_fenced "${res}"$'\n' fi - should_fail=1 } -warn() { - if [[ -n "${GITHUB_ACTIONS:-}" ]]; then - printf '::warning::%s\n' "$*" - else - printf >&2 'warning: %s\n' "$*" +check_alt() { + local recommended=$1 + local not_recommended=$2 + if [[ -n "$3" ]]; then + error "please use ${recommended} instead of ${not_recommended} for consistency" + print_fenced "$3"$'\n' fi } -info() { - printf >&2 'info: %s\n' "$*" +check_hidden() { + local res + for file in "$@"; do + check_alt ".${file}" "${file}" "$(comm -23 <(ls_files "*${file}") <(ls_files "*.${file}"))" + done } sed_rhs_escape() { sed 's/\\/\\\\/g; s/\&/\\\&/g; s/\//\\\//g' <<<"$1" @@ -124,7 +135,7 @@ fi exe='' py_suffix='' if type -P python3 >/dev/null; then - py_suffix='3' + py_suffix=3 fi venv_bin=.venv/bin yq() { @@ -137,7 +148,7 @@ tomlq() { } case "$(uname -s)" in Linux) - if [[ "$(uname -o)" == "Android" ]]; then + if [[ "$(uname -o)" == 'Android' ]]; then ostype=android else ostype=linux @@ -149,14 +160,14 @@ case "$(uname -s)" in OpenBSD) ostype=openbsd ;; DragonFly) ostype=dragonfly ;; SunOS) - if [[ "$(/usr/bin/uname -o)" == "illumos" ]]; then + if [[ "$(/usr/bin/uname -o)" == 'illumos' ]]; then ostype=illumos else ostype=solaris # Solaris /usr/bin/* are not POSIX-compliant (e.g., grep has no -q, -E, -F), # and POSIX-compliant commands are in /usr/xpg{4,6,7}/bin. # https://docs.oracle.com/cd/E88353_01/html/E37853/xpg-7.html - if [[ "${PATH}" != *"/usr/xpg4/bin"* ]]; then + if [[ "${PATH}" != *'/usr/xpg4/bin'* ]]; then export PATH="/usr/xpg4/bin:${PATH}" fi # GNU/BSD grep/sed is required to run some checks, but most checks are okay with other POSIX grep/sed. @@ -176,9 +187,9 @@ case "$(uname -s)" in if type -P jq >/dev/null; then # https://github.com/jqlang/jq/issues/1854 _tmp=$(jq -r .a <<<'{}') - if [[ "${_tmp}" != "null" ]]; then + if [[ "${_tmp}" != 'null' ]]; then _tmp=$(jq -b -r .a 2>/dev/null <<<'{}' || true) - if [[ "${_tmp}" == "null" ]]; then + if [[ "${_tmp}" == 'null' ]]; then jq() { command jq -b "$@"; } else jq() { command jq "$@" | tr -d '\r'; } @@ -213,7 +224,7 @@ while IFS=$'\n' read -r line; do exclude_from_ls_files_no_symlink+=("${line}"); git ls-files --deleted } | LC_ALL=C sort -u) ls_files() { - if [[ "${1:-}" == "--include-symlink" ]]; then + if [[ "${1:-}" == '--include-symlink' ]]; then shift comm -23 <(git ls-files "$@" | LC_ALL=C sort) <(printf '%s\n' ${exclude_from_ls_files_no_symlink[@]+"${exclude_from_ls_files_no_symlink[@]}"}) else @@ -263,7 +274,7 @@ if [[ -n "$(ls_files '*.rs')" ]]; then has_root_crate='' for pkg in $(jq -c '. as $metadata | .workspace_members[] as $id | $metadata.packages[] | select(.id == $id)' <<<"${metadata}"); do eval "$(jq -r '@sh "publish=\(.publish) manifest_path=\(.manifest_path)"' <<<"${pkg}")" - if [[ "$(tomlq -c '.lints' "${manifest_path}")" == "null" ]]; then + if [[ "$(tomlq -c '.lints' "${manifest_path}")" == 'null' ]]; then error "no [lints] table in ${manifest_path} please add '[lints]' with 'workspace = true'" fi # Publishing is unrestricted if null, and forbidden if an empty array. @@ -316,15 +327,11 @@ if [[ -n "$(ls_files '*.rs')" ]]; then done if [[ -n "${executables}" ]]; then error "file-permissions-check failed: executables are only allowed to be present in directories that are excluded from crates.io" - printf '=======================================\n' - printf '%s' "${executables}" - printf '=======================================\n\n' + print_fenced "${executables}" fi if [[ -n "${binaries}" ]]; then error "file-permissions-check failed: binaries are only allowed to be present in directories that are excluded from crates.io" - printf '=======================================\n' - printf '%s' "${binaries}" - printf '=======================================\n\n' + print_fenced "${binaries}" fi fi fi @@ -447,15 +454,10 @@ if [[ -n "$(ls_files '*.rs')" ]]; then check_diff "${lib}" done printf '\n' -elif [[ -n "$(ls_files '*.cargo' '*clippy.toml' '*deny.toml' '*rustfmt.toml' '*Cargo.toml' '*Cargo.lock')" ]]; then - error "the following files are unused because there is no Rust code; consider removing them" - printf '=======================================\n' - ls_files '*.cargo' '*clippy.toml' '*deny.toml' '*rustfmt.toml' '*Cargo.toml' '*Cargo.lock' - printf '=======================================\n\n' +else + check_unused "Rust code" '*.cargo*' '*clippy.toml' '*deny.toml' '*rustfmt.toml' '*Cargo.toml' '*Cargo.lock' fi -check_hidden clippy.toml -check_hidden deny.toml -check_hidden rustfmt.toml +check_hidden clippy.toml deny.toml rustfmt.toml # C/C++/Protobuf (if exists) clang_format_ext=('*.c' '*.h' '*.cpp' '*.hpp' '*.proto') @@ -470,28 +472,20 @@ if [[ -n "$(ls_files "${clang_format_ext[@]}")" ]]; then check_diff $(ls_files "${clang_format_ext[@]}") fi printf '\n' -elif [[ -n "$(ls_files '*.clang-format' '*_clang-format' '*.clang-format-ignore')" ]]; then - error "the following files are unused because there is no C/C++/Protobuf code; consider removing them" - printf '=======================================\n' - ls_files '*.clang-format' '*_clang-format' '*.clang-format-ignore' - printf '=======================================\n\n' -fi -if [[ -n "$(ls_files '*_clang-format')" ]]; then - error "please use '.clang-format' instead of '_clang-format' for consistency" - printf '=======================================\n' - ls_files '*_clang-format' - printf '=======================================\n\n' +else + check_unused "C/C++/Protobuf code" '*.clang-format*' fi +check_alt '.clang-format' '_clang-format' "$(ls_files '*_clang-format')" # https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html -check_alt '.cpp' '*.cc' '*.cp' '*.cxx' '*.C' '*.CPP' '*.c++' -check_alt '.hpp' '*.hh' '*.hp' '*.hxx' '*.H' '*.HPP' '*.h++' +check_alt '.cpp extension' 'other extensions' "$(ls_files '*.cc' '*.cp' '*.cxx' '*.C' '*.CPP' '*.c++')" +check_alt '.hpp extension' 'other extensions' "$(ls_files '*.hh' '*.hp' '*.hxx' '*.H' '*.HPP' '*.h++')" # YAML/HTML/CSS/JavaScript/JSON (if exists) prettier_ext=('*.css' '*.html' '*.js' '*.json' '*.yml' '*.yaml') if [[ -n "$(ls_files "${prettier_ext[@]}")" ]]; then info "checking YAML/HTML/CSS/JavaScript/JSON code style" check_config .editorconfig - if [[ "${ostype}" == "solaris" ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then + if [[ "${ostype}" == 'solaris' ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then warn "this check is skipped on Solaris due to no node 18+ in upstream package manager" elif check_install npm; then IFS=' ' @@ -501,26 +495,18 @@ if [[ -n "$(ls_files "${prettier_ext[@]}")" ]]; then check_diff $(ls_files "${prettier_ext[@]}") fi printf '\n' -elif [[ -n "$(ls_files '*.prettierignore')" ]]; then - error "the following files are unused because there is no YAML/JavaScript/JSON file; consider removing them" - printf '=======================================\n' - ls_files '*.prettierignore' - printf '=======================================\n\n' +else + check_unused "YAML/HTML/CSS/JavaScript/JSON file" '*.prettierignore' fi # https://prettier.io/docs/en/configuration -check_alt '.editorconfig' '*.prettierrc*' '*prettier.config.*' -if [[ -n "$(ls_files '*.yaml' | { grep -Fv '.markdownlint-cli2.yaml' || true; })" ]]; then - error "please use '.yml' instead of '.yaml' for consistency" - printf '=======================================\n' - ls_files '*.yaml' | { grep -Fv '.markdownlint-cli2.yaml' || true; } - printf '=======================================\n\n' -fi +check_alt '.editorconfig' 'other configs' "$(ls_files '*.prettierrc*' '*prettier.config.*')" +check_alt '.yml extension' '.yaml extension' "$(ls_files '*.yaml' | { grep -Fv '.markdownlint-cli2.yaml' || true; })" # TOML (if exists) if [[ -n "$(ls_files '*.toml' | { grep -Fv '.taplo.toml' || true; })" ]]; then info "checking TOML style" check_config .taplo.toml - if [[ "${ostype}" == "solaris" ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then + if [[ "${ostype}" == 'solaris' ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then warn "this check is skipped on Solaris due to no node 18+ in upstream package manager" elif check_install npm; then info "running \`npx -y @taplo/cli fmt \$(git ls-files '*.toml')\`" @@ -528,19 +514,16 @@ if [[ -n "$(ls_files '*.toml' | { grep -Fv '.taplo.toml' || true; })" ]]; then check_diff $(ls_files '*.toml') fi printf '\n' -elif [[ -n "$(ls_files '*taplo.toml')" ]]; then - error "the following files are unused because there is no TOML file; consider removing them" - printf '=======================================\n' - ls_files '*taplo.toml' - printf '=======================================\n\n' +else + check_unused "TOML file" '*taplo.toml' fi check_hidden taplo.toml # Markdown (if exists) if [[ -n "$(ls_files '*.md')" ]]; then - info "checking Markdown style" + info "checking markdown style" check_config .markdownlint-cli2.yaml - if [[ "${ostype}" == "solaris" ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then + if [[ "${ostype}" == 'solaris' ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then warn "this check is skipped on Solaris due to no node 18+ in upstream package manager" elif check_install npm; then info "running \`npx -y markdownlint-cli2 \$(git ls-files '*.md')\`" @@ -549,15 +532,12 @@ if [[ -n "$(ls_files '*.md')" ]]; then fi fi printf '\n' -elif [[ -n "$(ls_files '*.markdownlint-cli2.*')" ]]; then - error "the following files are unused because there is no markdown file; consider removing them" - printf '=======================================\n' - ls_files '*.markdownlint-cli2.*' - printf '=======================================\n\n' +else + check_unused "markdown file" '*.markdownlint-cli2.yaml' fi # https://github.com/DavidAnson/markdownlint-cli2#configuration -check_alt '.markdownlint-cli2.yaml' '*.markdownlint-cli2.jsonc' '*.markdownlint-cli2.cjs' '*.markdownlint-cli2.mjs' '*.markdownlint.*' -check_alt '.md' '*.markdown' +check_alt '.markdownlint-cli2.yaml' 'other configs' "$(ls_files '*.markdownlint-cli2.jsonc' '*.markdownlint-cli2.cjs' '*.markdownlint-cli2.mjs' '*.markdownlint.*')" +check_alt '.md extension' '*.markdown extension' "$(ls_files '*.markdown')" # Shell scripts info "checking shell scripts" @@ -573,7 +553,8 @@ for p in $(ls_files '*.sh' '*Dockerfile*'); do case "${p##*/}" in *.sh) shell_files+=("${p}") - if [[ "$(head -1 "${p}")" =~ ^#!/.*bash ]]; then + re='^#!/.*bash' + if [[ "$(head -1 "${p}")" =~ ${re} ]]; then bash_files+=("${p}") fi ;; @@ -599,7 +580,7 @@ if [[ -d .github/workflows ]]; then fi if [[ -n "$(ls_files '*action.yml')" ]]; then for p in $(ls_files '*action.yml'); do - if [[ "${p##*/}" == "action.yml" ]]; then + if [[ "${p##*/}" == 'action.yml' ]]; then actions+=("${p}") if ! grep -Fq 'shell: sh' "${p}"; then bash_files+=("${p}") @@ -611,54 +592,40 @@ fi res=$({ grep -En '(\[\[ .* ]]|(^|[^\$])\(\(.*\)\))( +#| *$)' "${bash_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "bare [[ ]] and (( )) may not work as intended: see https://github.com/koalaman/shellcheck/issues/2360 for more" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi # TODO: chmod|chown res=$({ grep -En '(^|[^0-9A-Za-z\."'\''-])(basename|cat|cd|cp|dirname|ln|ls|mkdir|mv|pushd|rm|rmdir|tee|touch)( +-[0-9A-Za-z]+)* +[^<>\|-]' "${bash_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "use \`--\` before path(s): see https://github.com/koalaman/shellcheck/issues/2707 / https://github.com/koalaman/shellcheck/issues/2612 / https://github.com/koalaman/shellcheck/issues/2305 / https://github.com/koalaman/shellcheck/issues/2157 / https://github.com/koalaman/shellcheck/issues/2121 / https://github.com/koalaman/shellcheck/issues/314 for more" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi res=$({ grep -En '(^|[^0-9A-Za-z\."'\''-])(LINES|RANDOM|PWD)=' "${bash_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "do not modify these built-in bash variables: see https://github.com/koalaman/shellcheck/issues/2160 / https://github.com/koalaman/shellcheck/issues/2559 for more" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi # perf res=$({ grep -En '(^|[^\\])\$\((cat) ' "${bash_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "use faster \`\$(' "${bash_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "\`type -P\` doesn't output to stderr; use \`>\` instead of \`&>\`" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi # TODO: multi-line case res=$({ grep -En '(^|[^0-9A-Za-z\."'\''-])(echo|printf )[^;)]* \|[^\|]' "${bash_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "use faster \`<<<...\` instead of \`echo ... |\`/\`printf ... |\`: see https://github.com/koalaman/shellcheck/issues/2593 for more" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi # style if [[ ${#grep_ere_files[@]} -gt 0 ]]; then @@ -667,18 +634,14 @@ if [[ ${#grep_ere_files[@]} -gt 0 ]]; then res=$({ grep -En '(^|[^0-9A-Za-z\."'\''-])(grep) +([^-]|-[^EFP-]|--[^hv])' "${grep_ere_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "please always use ERE (grep -E) instead of BRE for code consistency within a file" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi fi if [[ ${#sed_ere_files[@]} -gt 0 ]]; then res=$({ grep -En '(^|[^0-9A-Za-z\."'\''-])(sed) +([^-]|-[^E-]|--[^hv])' "${sed_ere_files[@]}" || true; } | { grep -Ev '^[^ ]+: *(#|//)' || true; } | LC_ALL=C sort) if [[ -n "${res}" ]]; then error "please always use ERE (sed -E) instead of BRE for code consistency within a file" - printf '=======================================\n' - printf '%s\n' "${res}" - printf '=======================================\n\n' + print_fenced "${res}"$'\n' fi fi if check_install shfmt; then @@ -689,7 +652,7 @@ if check_install shfmt; then fi check_diff "${shell_files[@]}" fi -if [[ "${ostype}" == "solaris" ]] && [[ -n "${CI:-}" ]] && ! type -P shellcheck >/dev/null; then +if [[ "${ostype}" == 'solaris' ]] && [[ -n "${CI:-}" ]] && ! type -P shellcheck >/dev/null; then warn "this check is skipped on Solaris due to no haskell/shellcheck in upstream package manager" elif check_install shellcheck; then check_config .shellcheckrc @@ -702,17 +665,17 @@ elif check_install shellcheck; then # Exclude SC2096 due to the way the temporary script is created. shellcheck_exclude=SC2096 info "running \`shellcheck --exclude ${shellcheck_exclude}\` for scripts in \$(git ls-files '*Dockerfile*')\`" - if [[ "${ostype}" == "windows" ]]; then + if [[ "${ostype}" == 'windows' ]]; then # No such file or directory: '/proc/N/fd/N' warn "this check is skipped on Windows due to upstream bug (failed to found fd created by <())" - elif [[ "${ostype}" == "dragonfly" ]]; then + elif [[ "${ostype}" == 'dragonfly' ]]; then warn "this check is skipped on DragonFly BSD due to upstream bug (hang)" elif check_install jq python3 parse-dockerfile; then shellcheck_for_dockerfile() { local text=$1 local shell=$2 local display_path=$3 - if [[ "${text}" == "null" ]]; then + if [[ "${text}" == 'null' ]]; then return fi text="#!${shell}"$'\n'"${text}" @@ -768,34 +731,34 @@ elif check_install shellcheck; then # only shell-form RUN/ENTRYPOINT/CMD is run in a shell case "${instruction_kind}" in RUN) - if [[ "$(jq -r '.arguments.shell' <<<"${instruction}")" == "null" ]]; then + if [[ "$(jq -r '.arguments.shell' <<<"${instruction}")" == 'null' ]]; then continue fi arguments=$(jq -r '.arguments.shell.value' <<<"${instruction}") if [[ -z "${arguments}" ]]; then - if [[ "$(jq -r '.here_docs[0]' <<<"${instruction}")" == "null" ]]; then + if [[ "$(jq -r '.here_docs[0]' <<<"${instruction}")" == 'null' ]]; then error "empty RUN is useless (${dockerfile_path})" continue fi - if [[ "$(jq -r '.here_docs[1]' <<<"${instruction}")" != "null" ]]; then + if [[ "$(jq -r '.here_docs[1]' <<<"${instruction}")" != 'null' ]]; then # TODO: error "multi here-docs without command is not yet supported (${dockerfile_path})" fi arguments=$(jq -r '.here_docs[0].value' <<<"${instruction}") - if [[ "${arguments}" == "#!"* ]]; then + if [[ "${arguments}" == '#!'* ]]; then # TODO: error "here-docs with shebang is not yet supported (${dockerfile_path})" continue fi else - if [[ "$(jq -r '.here_docs[0]' <<<"${instruction}")" != "null" ]]; then + if [[ "$(jq -r '.here_docs[0]' <<<"${instruction}")" != 'null' ]]; then # TODO: error "sh/bash command with here-docs is not yet checked (${dockerfile_path})" fi fi ;; ENTRYPOINT | CMD) - if [[ "$(jq -r '.arguments.shell' <<<"${instruction}")" == "null" ]]; then + if [[ "$(jq -r '.arguments.shell' <<<"${instruction}")" == 'null' ]]; then continue fi arguments=$(jq -r '.arguments.shell.value' <<<"${instruction}") @@ -808,7 +771,7 @@ elif check_install shellcheck; then if [[ "$(jq -r '.arguments.kind' <<<"${instruction}")" != "CMD" ]]; then continue fi - if [[ "$(jq -r '.arguments.arguments.shell' <<<"${instruction}")" == "null" ]]; then + if [[ "$(jq -r '.arguments.arguments.shell' <<<"${instruction}")" == 'null' ]]; then continue fi arguments=$(jq -r '.arguments.arguments.shell.value' <<<"${instruction}") @@ -832,17 +795,17 @@ elif check_install shellcheck; then # Exclude SC2096 due to the way the temporary script is created. shellcheck_exclude=SC2086,SC2096,SC2129 info "running \`shellcheck --exclude ${shellcheck_exclude}\` for scripts in .github/workflows/*.yml and **/action.yml" - if [[ "${ostype}" == "windows" ]]; then + if [[ "${ostype}" == 'windows' ]]; then # No such file or directory: '/proc/N/fd/N' warn "this check is skipped on Windows due to upstream bug (failed to found fd created by <())" - elif [[ "${ostype}" == "dragonfly" ]]; then + elif [[ "${ostype}" == 'dragonfly' ]]; then warn "this check is skipped on DragonFly BSD due to upstream bug (hang)" elif check_install jq python3; then shellcheck_for_gha() { local text=$1 local shell=$2 local display_path=$3 - if [[ "${text}" == "null" ]]; then + if [[ "${text}" == 'null' ]]; then return fi case "${shell}" in @@ -882,7 +845,8 @@ EOF esac default_shell=$(jq -r -c '.defaults.run.shell' <<<"${workflow}") # github's default is https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#defaultsrunshell - if [[ ! "${default_shell}" =~ ^bash\ --noprofile\ --norc\ -CeEux?o\ pipefail\ \{0}$ ]]; then + re='^bash --noprofile --norc -CeEux?o pipefail \{0}$' + if [[ ! "${default_shell}" =~ ${re} ]]; then error "${workflow_path}: defaults.run.shell should be 'bash --noprofile --norc -CeEuxo pipefail {0}' or 'bash --noprofile --norc -CeEuo pipefail {0}'" continue fi @@ -892,17 +856,17 @@ EOF job=$(jq -r '.value' <<<"${job}") n=0 job_default_shell=$(jq -r '.defaults.run.shell' <<<"${job}") - if [[ "${job_default_shell}" == "null" ]]; then + if [[ "${job_default_shell}" == 'null' ]]; then job_default_shell="${default_shell}" fi for step in $(jq -c '.steps[]' <<<"${job}"); do prepare='' eval "$(jq -r 'if .run then @sh "RUN=\(.run) shell=\(.shell)" else @sh "RUN=\(.with.run) prepare=\(.with.prepare) shell=\(.with.shell)" end' <<<"${step}")" - if [[ "${RUN}" == "null" ]]; then + if [[ "${RUN}" == 'null' ]]; then _=$((n++)) continue fi - if [[ "${shell}" == "null" ]]; then + if [[ "${shell}" == 'null' ]]; then if [[ -z "${prepare}" ]]; then shell="${job_default_shell}" elif grep -Eq '^ *chsh +-s +[^ ]+/bash' <<<"${prepare}"; then @@ -926,11 +890,11 @@ EOF for step in $(jq -c '.steps[]' <<<"${runs}"); do prepare='' eval "$(jq -r 'if .run then @sh "RUN=\(.run) shell=\(.shell)" else @sh "RUN=\(.with.run) prepare=\(.with.prepare) shell=\(.with.shell)" end' <<<"${step}")" - if [[ "${RUN}" == "null" ]]; then + if [[ "${RUN}" == 'null' ]]; then _=$((n++)) continue fi - if [[ "${shell}" == "null" ]]; then + if [[ "${shell}" == 'null' ]]; then if [[ -z "${prepare}" ]]; then error "\`shell: ..\` is required" continue @@ -949,7 +913,7 @@ EOF fi fi printf '\n' -check_alt '.sh' '*.bash' +check_alt '.sh extension' '*.bash extension' "$(ls_files '*.bash')" # License check # TODO: This check is still experimental and does not track all files that should be tracked. @@ -960,9 +924,9 @@ if [[ -f tools/.tidy-check-license-headers ]]; then case "${p##*/}" in *.stderr | *.expanded.rs) continue ;; # generated files *.json) continue ;; # no comment support - *.sh | *.py | *.rb | *Dockerfile*) prefix=("# ") ;; - *.rs | *.c | *.h | *.cpp | *.hpp | *.s | *.S | *.js) prefix=("// " "/* ") ;; - *.ld | *.x) prefix=("/* ") ;; + *.sh | *.py | *.rb | *Dockerfile*) prefix=('# ') ;; + *.rs | *.c | *.h | *.cpp | *.hpp | *.s | *.S | *.js) prefix=('// ' '/* ') ;; + *.ld | *.x) prefix=('/* ') ;; # TODO: More file types? *) continue ;; esac @@ -971,7 +935,7 @@ if [[ -f tools/.tidy-check-license-headers ]]; then line=1 if IFS= LC_ALL=C read -rd '' -n3 shebang <"${p}" && [[ "${shebang}" == '#!/' ]]; then line=2 - elif [[ "${p}" == *"Dockerfile"* ]] && IFS= LC_ALL=C read -rd '' -n9 syntax <"${p}" && [[ "${syntax}" == '# syntax=' ]]; then + elif [[ "${p}" == *'Dockerfile'* ]] && IFS= LC_ALL=C read -rd '' -n9 syntax <"${p}" && [[ "${syntax}" == '# syntax=' ]]; then line=2 fi header_found='' @@ -988,9 +952,7 @@ if [[ -f tools/.tidy-check-license-headers ]]; then done if [[ -n "${failed_files}" ]]; then error "license-check failed: please add SPDX-License-Identifier to the following files" - printf '=======================================\n' - printf '%s' "${failed_files}" - printf '=======================================\n\n' + print_fenced "${failed_files}" else printf '\n' fi @@ -1000,9 +962,9 @@ fi if [[ -f .cspell.json ]]; then info "spell checking" project_dictionary=.github/.cspell/project-dictionary.txt - if [[ "${ostype}" == "solaris" ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then + if [[ "${ostype}" == 'solaris' ]] && [[ -n "${CI:-}" ]] && ! type -P npm >/dev/null; then warn "this check is skipped on Solaris due to no node 18+ in upstream package manager" - elif [[ "${ostype}" == "illumos" ]]; then + elif [[ "${ostype}" == 'illumos' ]]; then warn "this check is skipped on illumos due to upstream bug (dictionaries are not loaded correctly)" elif check_install npm jq python3; then has_rust='' @@ -1010,7 +972,7 @@ if [[ -f .cspell.json ]]; then has_rust=1 dependencies='' for manifest_path in $(ls_files '*Cargo.toml'); do - if [[ "${manifest_path}" != "Cargo.toml" ]] && [[ "$(tomlq -c '.workspace' "${manifest_path}")" == "null" ]]; then + if [[ "${manifest_path}" != "Cargo.toml" ]] && [[ "$(tomlq -c '.workspace' "${manifest_path}")" == 'null' ]]; then continue fi m=$(cargo metadata --format-version=1 --no-deps --manifest-path "${manifest_path}" || true) @@ -1053,7 +1015,7 @@ EOF if ! ls_files | npx -y cspell --file-list stdin --no-progress --no-summary; then error "spellcheck failed: please fix uses of below words or add to ${project_dictionary} if correct" printf '=======================================\n' - { ls_files | npx -y cspell --file-list stdin --no-progress --no-summary --words-only || true; } | LC_ALL=C sort -f -u + { ls_files | npx -y cspell --file-list stdin --no-progress --no-summary --words-only || true; } | sed "s/'s$//g" | LC_ALL=C sort -f -u printf '=======================================\n\n' fi @@ -1069,16 +1031,14 @@ EOF esac if [[ -n "${dup}" ]]; then error "duplicated words in dictionaries; please remove the following words from ${project_dictionary}" - printf '=======================================\n' - printf '%s\n' "${dup}" - printf '=======================================\n\n' + print_fenced "${dup}"$'\n' fi done # Make sure the project-specific dictionary does not contain unused words. if [[ -n "${REMOVE_UNUSED_WORDS:-}" ]]; then grep_args=() - for word in $(grep -Ev '^//.*' "${project_dictionary}" || true); do + for word in $(grep -Ev '^//' "${project_dictionary}" || true); do if ! grep -Eqi "^${word}$" <<<"${all_words}"; then grep_args+=(-e "^${word}$") fi @@ -1090,16 +1050,14 @@ EOF fi else unused='' - for word in $(grep -Ev '^//.*' "${project_dictionary}" || true); do + for word in $(grep -Ev '^//' "${project_dictionary}" || true); do if ! grep -Eqi "^${word}$" <<<"${all_words}"; then unused+="${word}"$'\n' fi done if [[ -n "${unused}" ]]; then error "unused words in dictionaries; please remove the following words from ${project_dictionary} or run ${0##*/} locally" - printf '=======================================\n' - printf '%s' "${unused}" - printf '=======================================\n\n' + print_fenced "${unused}" fi fi fi