@@ -8,7 +8,7 @@ trap -- 'printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
88cd -- " $( dirname -- " $0 " ) " /..
99
1010# USAGE:
11- # ./tools/tidy.sh
11+ # GH_TOKEN=$(gh auth token) ./tools/tidy.sh
1212#
1313# Note: This script requires the following tools:
1414# - git 1.8+
@@ -17,6 +17,7 @@ cd -- "$(dirname -- "$0")"/..
1717# - python 3.6+ and pipx
1818# - shfmt
1919# - shellcheck
20+ # - zizmor
2021# - cargo, rustfmt (if Rust code exists)
2122# - clang-format (if C/C++/Protobuf code exists)
2223# - parse-dockerfile <https://github.com/taiki-e/parse-dockerfile> (if Dockerfile exists)
@@ -117,11 +118,11 @@ check_alt() {
117118check_hidden () {
118119 local res
119120 for file in " $@ " ; do
120- check_alt " .${file} " " ${file} " " $( comm -23 <( ls_files " *${file} " ) <( ls_files " *.${file} " ) ) "
121+ check_alt " .${file} " " ${file} " " $( LC_ALL=C comm -23 <( ls_files " *${file} " ) <( ls_files " *.${file} " ) ) "
121122 done
122123}
123124sed_rhs_escape () {
124- sed ' s/\\/\\\\/g; s/\&/\\\&/g; s/\//\\\//g' <<< " $1"
125+ sed -E ' s/\\/\\\\/g; s/\&/\\\&/g; s/\//\\\//g' <<< " $1"
125126}
126127
127128if [[ $# -gt 0 ]]; then
@@ -136,12 +137,8 @@ py_suffix=''
136137if type -P python3 > /dev/null; then
137138 py_suffix=3
138139fi
139- yq () {
140- pipx run yq " $@ "
141- }
142- tomlq () {
143- pipx run --spec yq tomlq " $@ "
144- }
140+ yq () { pipx run yq " $@ " ; }
141+ tomlq () { pipx run --spec yq tomlq " $@ " ; }
145142case " $( uname -s) " in
146143 Linux)
147144 if [[ " $( uname -o) " == ' Android' ]]; then
@@ -166,10 +163,11 @@ case "$(uname -s)" in
166163 if [[ " ${PATH} " != * ' /usr/xpg4/bin' * ]]; then
167164 export PATH=" /usr/xpg4/bin:${PATH} "
168165 fi
169- # GNU/BSD grep/sed is required to run some checks, but most checks are okay with other POSIX grep/sed.
166+ # GNU/BSD sed is required.
167+ # GNU/BSD grep is required by some checks, but most checks are okay with other POSIX grep.
170168 # Solaris /usr/xpg4/bin/grep has -q, -E, -F, but no -o (non-POSIX).
171169 # Solaris /usr/xpg4/bin/sed has no -E (POSIX.1-2024) yet.
172- for tool in sed grep; do
170+ for tool in ' grep' ' sed ' ; do
173171 if type -P " g${tool} " > /dev/null; then
174172 eval " ${tool} () { g${tool} \"\$ @\" ; }"
175173 fi
@@ -188,12 +186,8 @@ case "$(uname -s)" in
188186 else
189187 jq () { command jq " $@ " | tr -d ' \r' ; }
190188 fi
191- yq () {
192- pipx run yq " $@ " | tr -d ' \r'
193- }
194- tomlq () {
195- pipx run --spec yq tomlq " $@ " | tr -d ' \r'
196- }
189+ yq () { pipx run yq " $@ " | tr -d ' \r' ; }
190+ tomlq () { pipx run --spec yq tomlq " $@ " | tr -d ' \r' ; }
197191 fi
198192 fi
199193 ;;
@@ -202,25 +196,34 @@ esac
202196
203197check_install git
204198exclude_from_ls_files=()
205- # - `find` lists symlinks. `! ( -name <dir> -prune )` (.i.e., ignore <dir>) are manually listed from .gitignore.
206- # - `git submodule status` lists submodules. Use sed to remove the first character indicates status ( |+|-).
199+ # - `find` lists symlinks. `! ( -name <dir> -prune )` means recursively ignore <dir>. `cut` removes the leading `./`.
200+ # This can be replaced with `fd -H -t l`.
201+ # - `git submodule status` lists submodules. The first `cut` removes the first character indicates status ( |+|-).
207202# - `git ls-files --deleted` lists removed files.
208- while IFS=$' \n ' read -r line; do exclude_from_ls_files+=(" ${line} " ); done < <( {
209- find . \! \( -name .git -prune \) \! \( -name target -prune \) \! \( -name tmp -prune \) -type l | cut -c3-
210- git submodule status | sed ' s/^.//' | cut -d' ' -f2
203+ find_prune=(\! \( -name .git -prune \) )
204+ while IFS= read -r; do
205+ find_prune+=(\! \( -name " ${REPLY} " -prune \) )
206+ done < <( sed -E ' s/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d' .gitignore)
207+ while IFS=$' \n ' read -r; do
208+ exclude_from_ls_files+=(" ${REPLY} " )
209+ done < <( {
210+ find . " ${find_prune[@]} " -type l | cut -c3-
211+ git submodule status | cut -c2- | cut -d' ' -f2
211212 git ls-files --deleted
212213} | LC_ALL=C sort -u)
213214exclude_from_ls_files_no_symlink=()
214- while IFS=$' \n ' read -r line; do exclude_from_ls_files_no_symlink+=(" ${line} " ); done < <( {
215- git submodule status | sed ' s/^.//' | cut -d' ' -f2
215+ while IFS=$' \n ' read -r; do
216+ exclude_from_ls_files_no_symlink+=(" ${REPLY} " )
217+ done < <( {
218+ git submodule status | cut -c2- | cut -d' ' -f2
216219 git ls-files --deleted
217220} | LC_ALL=C sort -u)
218221ls_files () {
219222 if [[ " ${1:- } " == ' --include-symlink' ]]; then
220223 shift
221- comm -23 <( git ls-files " $@ " | LC_ALL=C sort) <( printf ' %s\n' ${exclude_from_ls_files_no_symlink[@]+" ${exclude_from_ls_files_no_symlink[@]} " } )
224+ LC_ALL=C comm -23 <( git ls-files " $@ " | LC_ALL=C sort) <( printf ' %s\n' ${exclude_from_ls_files_no_symlink[@]+" ${exclude_from_ls_files_no_symlink[@]} " } )
222225 else
223- comm -23 <( git ls-files " $@ " | LC_ALL=C sort) <( printf ' %s\n' ${exclude_from_ls_files[@]+" ${exclude_from_ls_files[@]} " } )
226+ LC_ALL=C comm -23 <( git ls-files " $@ " | LC_ALL=C sort) <( printf ' %s\n' ${exclude_from_ls_files[@]+" ${exclude_from_ls_files[@]} " } )
224227 fi
225228}
226229
@@ -444,7 +447,7 @@ if [[ -n "$(ls_files '*.rs')" ]]; then
444447 new+=" ${line} " $' \a '
445448 done < <( tr ' \n' ' \a' < " ${markdown} " | grep -Eo ' <!-- tidy:sync-markdown-to-rustdoc:start[^ ]* -->.*<!-- tidy:sync-markdown-to-rustdoc:end -->' )
446449 new+=' <!-- tidy:sync-markdown-to-rustdoc:end -->'
447- new=$( tr ' \n' ' \a' < " ${lib} " | sed " s/<!-- tidy:sync-markdown-to-rustdoc:start[^ ]* -->.*<!-- tidy:sync-markdown-to-rustdoc:end -->/$( sed_rhs_escape " ${new} " ) /" | tr ' \a' ' \n' )
450+ new=$( tr ' \n' ' \a' < " ${lib} " | sed -E " s/<!-- tidy:sync-markdown-to-rustdoc:start[^ ]* -->.*<!-- tidy:sync-markdown-to-rustdoc:end -->/$( sed_rhs_escape " ${new} " ) /" | tr ' \a' ' \n' )
448451 printf ' %s\n' " ${new} " > | " ${lib} "
449452 check_diff " ${lib} "
450453 done
@@ -676,7 +679,7 @@ elif check_install shellcheck; then
676679 # Others: false negative
677680 trap -- ' rm -- ./tools/.tidy-tmp; printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
678681 printf ' %s\n' " ${text} " > | ./tools/.tidy-tmp
679- if ! shellcheck --color=" ${color} " --exclude " ${shellcheck_exclude} " ./tools/.tidy-tmp | sed " s/\.\/tools\/\.tidy-tmp/$( sed_rhs_escape " ${display_path} " ) /g" ; then
682+ if ! shellcheck --color=" ${color} " --exclude " ${shellcheck_exclude} " ./tools/.tidy-tmp | sed -E " s/\.\/tools\/\.tidy-tmp/$( sed_rhs_escape " ${display_path} " ) /g" ; then
680683 error " check failed; please resolve the above shellcheck error(s)"
681684 fi
682685 rm -- ./tools/.tidy-tmp
821824 # Others: false negative
822825 trap -- ' rm -- ./tools/.tidy-tmp; printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
823826 printf ' %s\n' " ${text} " > | ./tools/.tidy-tmp
824- if ! shellcheck --color=" ${color} " --exclude " ${shellcheck_exclude} " ./tools/.tidy-tmp | sed " s/\.\/tools\/\.tidy-tmp/$( sed_rhs_escape " ${display_path} " ) /g" ; then
827+ if ! shellcheck --color=" ${color} " --exclude " ${shellcheck_exclude} " ./tools/.tidy-tmp | sed -E " s/\.\/tools\/\.tidy-tmp/$( sed_rhs_escape " ${display_path} " ) /g" ; then
825828 error " check failed; please resolve the above shellcheck error(s)"
826829 fi
827830 rm -- ./tools/.tidy-tmp
832835 # The top-level permissions must be weak as they are referenced by all jobs.
833836 permissions=$( jq -c ' .permissions' <<< " ${workflow}" )
834837 case " ${permissions} " in
835- ' {"contents":"read"}' | ' {"contents":"none"}' ) ;;
838+ # `permissions: {}` means "all none": https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#defining-access-for-the-github_token-scopes
839+ ' {"contents":"read"}' | ' {}' ) ;;
836840 null) error " ${workflow_path} : top level permissions not found; it must be 'contents: read' or weaker permissions" ;;
837841 * ) error " ${workflow_path} : only 'contents: read' and weaker permissions are allowed at top level, but found '${permissions} '; if you want to use stronger permissions, please set job-level permissions" ;;
838842 esac
905909 fi
906910 fi
907911fi
912+ zizmor_targets=(${workflows[@]+" ${workflows[@]} " } ${actions[@]+" ${actions[@]} " } )
913+ if [[ -e .github/dependabot.yml ]]; then
914+ zizmor_targets+=(.github/dependabot.yml)
915+ fi
916+ if [[ ${# zizmor_targets[@]} -gt 0 ]]; then
917+ if [[ " ${ostype} " =~ ^(netbsd| openbsd| dragonfly| illumos| solaris)$ ]] && [[ -n " ${CI:- } " ]] && ! type -P zizmor > /dev/null; then
918+ warn " this check is skipped on NetBSD/OpenBSD/Dragonfly/illumos/Solaris due to installing zizmor is hard on these platform"
919+ elif check_install zizmor; then
920+ IFS=' '
921+ info " running \` zizmor -q ${zizmor_targets[*]} \` "
922+ IFS=$' \n\t '
923+ zizmor -q " ${zizmor_targets[@]} "
924+ fi
925+ fi
908926printf ' \n'
909927check_alt ' .sh extension' ' *.bash extension' " $( ls_files ' *.bash' ) "
910928
@@ -913,7 +931,7 @@ check_alt '.sh extension' '*.bash extension' "$(ls_files '*.bash')"
913931if [[ -f tools/.tidy-check-license-headers ]]; then
914932 info " checking license headers (experimental)"
915933 failed_files=' '
916- for p in $( comm -12 <( eval $( < tools/.tidy-check-license-headers) | LC_ALL=C sort) <( ls_files | LC_ALL=C sort) ) ; do
934+ for p in $( LC_ALL=C comm -12 <( eval $( < tools/.tidy-check-license-headers) | LC_ALL=C sort) <( ls_files | LC_ALL=C sort) ) ; do
917935 case " ${p##*/ } " in
918936 * .stderr | * .expanded.rs) continue ;; # generated files
919937 * .json) continue ;; # no comment support
@@ -981,11 +999,12 @@ if [[ -f .cspell.json ]]; then
981999 dependencies_words=$( npx -y cspell stdin --no-progress --no-summary --words-only --unique <<< " ${dependencies}" || true)
9821000 fi
9831001 all_words=$( ls_files | { grep -Fv " ${project_dictionary} " || true ; } | npx -y cspell --file-list stdin --no-progress --no-summary --words-only --unique || true)
1002+ all_words+=$' \n ' $( ls_files | npx -y cspell stdin --no-progress --no-summary --words-only --unique || true)
9841003 printf ' %s\n' " ${config_old} " > | .cspell.json
9851004 trap -- ' printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
9861005 cat > | .github/.cspell/rust-dependencies.txt << EOF
987- // This file is @generated by ${0##*/ } .
988- // It is not intended for manual editing.
1006+ # This file is @generated by ${0##*/ } .
1007+ # It is not intended for manual editing.
9891008EOF
9901009 if [[ -n " ${dependencies_words} " ]]; then
9911010 LC_ALL=C sort -f >> .github/.cspell/rust-dependencies.txt <<< " ${dependencies_words}" $' \n '
@@ -1000,11 +1019,20 @@ EOF
10001019 error " you may want to mark .github/.cspell/rust-dependencies.txt linguist-generated"
10011020 fi
10021021
1022+ # Check file names.
1023+ info " running \` git ls-files | npx -y cspell stdin --no-progress --no-summary --show-context\` "
1024+ if ! ls_files | npx -y cspell stdin --no-progress --no-summary --show-context; then
1025+ error " spellcheck failed: please fix uses of below words in file names or add to ${project_dictionary} if correct"
1026+ printf ' =======================================\n'
1027+ { ls_files | npx -y cspell stdin --no-progress --no-summary --words-only || true ; } | sed -E " s/'s$//g" | LC_ALL=C sort -f -u
1028+ printf ' =======================================\n\n'
1029+ fi
1030+ # Check file contains.
10031031 info " running \` git ls-files | npx -y cspell --file-list stdin --no-progress --no-summary\` "
10041032 if ! ls_files | npx -y cspell --file-list stdin --no-progress --no-summary; then
10051033 error " spellcheck failed: please fix uses of below words or add to ${project_dictionary} if correct"
10061034 printf ' =======================================\n'
1007- { 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
1035+ { ls_files | npx -y cspell --file-list stdin --no-progress --no-summary --words-only || true ; } | sed -E " s/'s$//g" | LC_ALL=C sort -f -u
10081036 printf ' =======================================\n\n'
10091037 fi
10101038
@@ -1015,8 +1043,8 @@ EOF
10151043 fi
10161044 case " ${ostype} " in
10171045 # NetBSD uniq doesn't support -i flag.
1018- netbsd) dup=$( sed ' /^$/d; /^\/\/ /d' " ${project_dictionary} " " ${dictionary} " | LC_ALL=C sort -f | tr ' [:upper:]' ' [:lower:]' | LC_ALL=C uniq -d) ;;
1019- * ) dup=$( sed ' /^$/d; /^\/\/ /d' " ${project_dictionary} " " ${dictionary} " | LC_ALL=C sort -f | LC_ALL=C uniq -d -i) ;;
1046+ netbsd) dup=$( sed -E ' s/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$ /d' " ${project_dictionary} " " ${dictionary} " | LC_ALL=C sort -f | tr ' [:upper:]' ' [:lower:]' | LC_ALL=C uniq -d) ;;
1047+ * ) dup=$( sed -E ' s/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$ /d' " ${project_dictionary} " " ${dictionary} " | LC_ALL=C sort -f | LC_ALL=C uniq -d -i) ;;
10201048 esac
10211049 if [[ -n " ${dup} " ]]; then
10221050 error " duplicated words in dictionaries; please remove the following words from ${project_dictionary} "
@@ -1027,13 +1055,14 @@ EOF
10271055 # Make sure the project-specific dictionary does not contain unused words.
10281056 if [[ -n " ${REMOVE_UNUSED_WORDS:- } " ]]; then
10291057 grep_args=()
1030- for word in $( grep -Ev ' ^// ' " ${project_dictionary} " || true ) ; do
1058+ while IFS= read -r word ; do
10311059 if ! grep -Eqi " ^${word} $" <<< " ${all_words}" ; then
1032- grep_args+=(-e " ^${word} $ " )
1060+ grep_args+=(-e " ^[ \t]* ${word} [ \t]*(#.*|$) " )
10331061 fi
1034- done
1062+ done < <( sed -E ' s/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d ' " ${project_dictionary} " )
10351063 if [[ ${# grep_args[@]} -gt 0 ]]; then
10361064 info " removing unused words from ${project_dictionary} "
1065+ info " please commit changes made by the removal above"
10371066 res=$( grep -Ev " ${grep_args[@]} " " ${project_dictionary} " || true)
10381067 if [[ -n " ${res} " ]]; then
10391068 printf ' %s\n' " ${res} " > | " ${project_dictionary} "
@@ -1043,11 +1072,11 @@ EOF
10431072 fi
10441073 else
10451074 unused=' '
1046- for word in $( grep -Ev ' ^// ' " ${project_dictionary} " || true ) ; do
1075+ while IFS= read -r word ; do
10471076 if ! grep -Eqi " ^${word} $" <<< " ${all_words}" ; then
10481077 unused+=" ${word} " $' \n '
10491078 fi
1050- done
1079+ done < <( sed -E ' s/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d ' " ${project_dictionary} " )
10511080 if [[ -n " ${unused} " ]]; then
10521081 error " unused words in dictionaries; please remove the following words from ${project_dictionary} or run ${0##*/ } locally"
10531082 print_fenced " ${unused} "
0 commit comments